src/experiments.rs

branch
dev
changeset 61
4f468d35fa29
parent 45
5200e7090e06
--- a/src/experiments.rs	Sun Apr 27 15:03:51 2025 -0500
+++ b/src/experiments.rs	Thu Feb 26 11:38:43 2026 -0500
@@ -3,37 +3,30 @@
 */
 
 //use numeric_literals::replace_float_literals;
-use serde::{Serialize, Deserialize};
-use clap::ValueEnum;
-use std::collections::HashMap;
-use std::hash::{Hash, Hasher};
-use std::collections::hash_map::DefaultHasher;
-
+use crate::kernels::SupportProductFirst as Prod;
+use crate::kernels::*;
+use crate::run::{DefaultAlgorithm, ExperimentBiased, ExperimentV2, Named, RunnableExperiment};
+use crate::types::*;
+use crate::{AlgorithmOverrides, ExperimentSetup};
 use alg_tools::bisection_tree::*;
 use alg_tools::error::DynResult;
 use alg_tools::norms::Linfinity;
-
-use crate::{ExperimentOverrides, AlgorithmOverrides};
-use crate::kernels::*;
-use crate::kernels::SupportProductFirst as Prod;
-use crate::types::*;
-use crate::run::{
-    RunnableExperiment,
-    ExperimentV2,
-    ExperimentBiased,
-    Named,
-    DefaultAlgorithm,
-};
-//use crate::fb::FBGenericConfig;
-use crate::rand_distr::{SerializableNormal, SaltAndPepper};
+use clap::{Parser, ValueEnum};
+use serde::{Deserialize, Serialize};
+use std::collections::hash_map::DefaultHasher;
+use std::collections::HashMap;
+use std::hash::{Hash, Hasher};
+//use crate::fb::InsertionConfig;
+use crate::rand_distr::{SaltAndPepper, SerializableNormal};
 use crate::regularisation::Regularisation;
 use alg_tools::euclidean::Euclidean;
 use alg_tools::instance::Instance;
 use alg_tools::mapping::Mapping;
 use alg_tools::operator_arithmetic::{MappingSum, Weighted};
+use itertools::Itertools;
+use serde_with::skip_serializing_none;
 
 /// Experiments shorthands, to be used with the command line parser
-
 #[derive(ValueEnum, Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 #[allow(non_camel_case_types)]
 pub enum DefaultExperiment {
@@ -69,49 +62,77 @@
     Experiment2D_TV_Fast,
 }
 
+/// Command line experiment setup overrides
+#[skip_serializing_none]
+#[derive(Parser, Debug, Serialize, Deserialize, Default, Clone, Hash)]
+pub struct DefaultExperimentSetup<F: ClapFloat> {
+    /// List of experiments to perform
+    #[arg(value_name = "EXPERIMENT")]
+    experiments: Vec<DefaultExperiment>,
+
+    #[arg(long)]
+    /// Regularisation parameter override.
+    ///
+    /// Only use if running just a single experiment, as different experiments have different
+    /// regularisation parameters.
+    alpha: Option<F>,
+
+    #[arg(long)]
+    /// Gaussian noise variance override
+    variance: Option<F>,
+
+    #[arg(long, value_names = &["MAGNITUDE", "PROBABILITY"])]
+    /// Salt and pepper noise override.
+    salt_and_pepper: Option<Vec<F>>,
+
+    #[arg(long)]
+    /// Noise seed
+    noise_seed: Option<u64>,
+}
+
 macro_rules! make_float_constant {
     ($name:ident = $value:expr) => {
         #[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)]
         #[serde(into = "float")]
         struct $name;
-        impl Into<float> for $name {
+        impl Into<f64> for $name {
             #[inline]
-            fn into(self) -> float { $value }
+            fn into(self) -> float {
+                $value
+            }
         }
         impl Constant for $name {
             type Type = float;
-            fn value(&self) -> float { $value }
+            fn value(&self) -> float {
+                $value
+            }
         }
-    }
+    };
 }
 
 /// Ground-truth measure spike locations and magnitudes for 1D experiments
-static MU_TRUE_1D_BASIC : [(float, float); 4] = [
-    (0.10, 10.0),
-    (0.30, 2.0),
-    (0.70, 3.0),
-    (0.80, 5.0)
-];
+static MU_TRUE_1D_BASIC: [(float, float); 4] =
+    [(0.10, 10.0), (0.30, 2.0), (0.70, 3.0), (0.80, 5.0)];
 
 /// Ground-truth measure spike locations and magnitudes for 2D experiments
-static MU_TRUE_2D_BASIC : [([float; 2], float); 4] = [
+static MU_TRUE_2D_BASIC: [([float; 2], float); 4] = [
     ([0.15, 0.15], 10.0),
     ([0.75, 0.45], 2.0),
     ([0.80, 0.50], 4.0),
-    ([0.30, 0.70], 5.0)
+    ([0.30, 0.70], 5.0),
 ];
 
 /// The $\{0,1\}$-valued characteristic function of a ball as a [`Mapping`].
-#[derive(Debug,Copy,Clone,Serialize,PartialEq)]
-struct BallCharacteristic<F : Float, const N : usize> {
-    pub center : Loc<F, N>,
-    pub radius : F,
+#[derive(Debug, Copy, Clone, Serialize, PartialEq)]
+struct BallCharacteristic<F: Float, const N: usize> {
+    pub center: Loc<N, F>,
+    pub radius: F,
 }
 
-impl<F : Float, const N : usize> Mapping<Loc<F, N>> for BallCharacteristic<F, N> {
-    type Codomain =F;
+impl<F: Float, const N: usize> Mapping<Loc<N, F>> for BallCharacteristic<F, N> {
+    type Codomain = F;
 
-    fn apply<I : Instance<Loc<F, N>>>(&self, i : I) -> F {
+    fn apply<I: Instance<Loc<N, F>>>(&self, i: I) -> F {
         if self.center.dist2(i) <= self.radius {
             F::ONE
         } else {
@@ -120,25 +141,52 @@
     }
 }
 
+/// Trait for customising the experiments available from the command line
+impl ExperimentSetup for DefaultExperimentSetup<f64> {
+    type FloatType = f64;
+
+    fn runnables(&self) -> DynResult<Vec<Box<dyn RunnableExperiment<Self::FloatType>>>> {
+        self.experiments
+            .iter()
+            .unique()
+            .map(|e| e.get_experiment(self))
+            .try_collect()
+    }
+}
+
 //#[replace_float_literals(F::cast_from(literal))]
 impl DefaultExperiment {
+    // fn default_list() -> Vec<Self> {
+    //     use DefaultExperiment::*;
+    //     [
+    //         Experiment1D,
+    //         Experiment1DFast,
+    //         Experiment2D,
+    //         Experiment2DFast,
+    //         Experiment1D_L1,
+    //     ]
+    //     .into()
+    // }
+
     /// Convert the experiment shorthand into a runnable experiment configuration.
-    pub fn get_experiment(&self, cli : &ExperimentOverrides<float>) -> DynResult<Box<dyn RunnableExperiment<float>>> {
-        let name = "pointsource".to_string()
-                                + self.to_possible_value().unwrap().get_name();
+    fn get_experiment(
+        &self,
+        cli: &DefaultExperimentSetup<f64>,
+    ) -> DynResult<Box<dyn RunnableExperiment<f64>>> {
+        let name = "pointsource".to_string() + self.to_possible_value().unwrap().get_name();
 
         let kernel_plot_width = 0.2;
 
-        const BASE_SEED : u64 = 915373234;
+        const BASE_SEED: u64 = 915373234;
 
-        const N_SENSORS_1D : usize = 100;
-        make_float_constant!(SensorWidth1D = 0.4/(N_SENSORS_1D as float));
+        const N_SENSORS_1D: usize = 100;
+        make_float_constant!(SensorWidth1D = 0.4 / (N_SENSORS_1D as float));
 
-        const N_SENSORS_2D : usize = 16;
-        make_float_constant!(SensorWidth2D = 0.4/(N_SENSORS_2D as float));
+        const N_SENSORS_2D: usize = 16;
+        make_float_constant!(SensorWidth2D = 0.4 / (N_SENSORS_2D as float));
 
-        const N_SENSORS_2D_MORE : usize = 32;
-        make_float_constant!(SensorWidth2DMore = 0.4/(N_SENSORS_2D_MORE as float));
+        //const N_SENSORS_2D_MORE: usize = 32;
+        //make_float_constant!(SensorWidth2DMore = 0.4 / (N_SENSORS_2D_MORE as float));
 
         make_float_constant!(Variance1 = 0.05.powi(2));
         make_float_constant!(CutOff1 = 0.15);
@@ -160,45 +208,43 @@
         //         .. Default::default()
         //     }
         // );
-        let sliding_fb_cut_gaussian = (DefaultAlgorithm::SlidingFB,
-            AlgorithmOverrides {
-                theta0 : Some(0.3),
-                .. Default::default()
-            }
-        );
+        let sliding_fb_cut_gaussian = (DefaultAlgorithm::SlidingFB, AlgorithmOverrides {
+            theta0: Some(0.3),
+            ..Default::default()
+        });
         // let higher_cpos = |alg| (alg,
         //     AlgorithmOverrides {
         //         transport_tolerance_pos : Some(1000.0),
         //         .. Default::default()
         //     }
         // );
-        let higher_cpos_merging = |alg| (alg,
-            AlgorithmOverrides {
-                transport_tolerance_pos : Some(1000.0),
-                merge : Some(true),
-                fitness_merging : Some(true),
-                .. Default::default()
-            }
-        );
-        let higher_cpos_merging_steptune = |alg| (alg,
-            AlgorithmOverrides {
-                transport_tolerance_pos : Some(1000.0),
-                theta0 : Some(0.3),
-                merge : Some(true),
-                fitness_merging : Some(true),
-                .. Default::default()
-            }
-        );
-        let much_higher_cpos_merging_steptune = |alg| (alg,
-            AlgorithmOverrides {
-                transport_tolerance_pos : Some(10000.0),
-                sigma0 : Some(0.15),
-                theta0 : Some(0.3),
-                merge : Some(true),
-                fitness_merging : Some(true),
-                .. Default::default()
-            }
-        );
+        let higher_cpos_merging = |alg| {
+            (alg, AlgorithmOverrides {
+                transport_tolerance_pos: Some(1000.0),
+                merge: Some(true),
+                fitness_merging: Some(true),
+                ..Default::default()
+            })
+        };
+        let higher_cpos_merging_steptune = |alg| {
+            (alg, AlgorithmOverrides {
+                transport_tolerance_pos: Some(1000.0),
+                theta0: Some(0.3),
+                merge: Some(true),
+                fitness_merging: Some(true),
+                ..Default::default()
+            })
+        };
+        let much_higher_cpos_merging_steptune = |alg| {
+            (alg, AlgorithmOverrides {
+                transport_tolerance_pos: Some(10000.0),
+                sigma0: Some(0.15),
+                theta0: Some(0.3),
+                merge: Some(true),
+                fitness_merging: Some(true),
+                ..Default::default()
+            })
+        };
         //  We add a hash of the experiment name to the configured
         // noise seed to not use the same noise for different experiments.
         let mut h = DefaultHasher::new();
@@ -210,238 +256,284 @@
         use DefaultExperiment::*;
         Ok(match self {
             Experiment1D => {
-                let base_spread = Gaussian { variance : Variance1 };
-                let spread_cutoff = BallIndicator { r : CutOff1, exponent : Linfinity };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]].into(),
-                    sensor_count : [N_SENSORS_1D],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.08)),
-                    noise_distr : SerializableNormal::new(0.0, cli.variance.unwrap_or(0.2))?,
-                    dataterm : DataTerm::L2Squared,
-                    μ_hat : MU_TRUE_1D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth1D, exponent : Linfinity },
-                    spread : Prod(spread_cutoff, base_spread),
-                    kernel : Prod(AutoConvolution(spread_cutoff), base_spread),
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::from([
-                        sliding_fb_cut_gaussian,
-                        higher_cpos_merging(DefaultAlgorithm::RadonFB),
-                        higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
-                    ]),
-                }})
-            },
+                let base_spread = Gaussian { variance: Variance1 };
+                let spread_cutoff = BallIndicator { r: CutOff1, exponent: Linfinity };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]].into(),
+                        sensor_count: [N_SENSORS_1D],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.08)),
+                        noise_distr: SerializableNormal::new(0.0, cli.variance.unwrap_or(0.2))?,
+                        dataterm: DataTermType::L222,
+                        μ_hat: MU_TRUE_1D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth1D, exponent: Linfinity },
+                        spread: Prod(spread_cutoff, base_spread),
+                        kernel: Prod(AutoConvolution(spread_cutoff), base_spread),
+                        kernel_plot_width,
+                        noise_seed,
+                        default_merge_radius,
+                        algorithm_overrides: HashMap::from([
+                            sliding_fb_cut_gaussian,
+                            higher_cpos_merging(DefaultAlgorithm::RadonFB),
+                            higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
+                        ]),
+                    },
+                })
+            }
             Experiment1DFast => {
-                let base_spread = HatConv { radius : Hat1 };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]].into(),
-                    sensor_count : [N_SENSORS_1D],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.06)),
-                    noise_distr : SerializableNormal::new(0.0, cli.variance.unwrap_or(0.2))?,
-                    dataterm : DataTerm::L2Squared,
-                    μ_hat : MU_TRUE_1D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth1D, exponent : Linfinity },
-                    spread : base_spread,
-                    kernel : base_spread,
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::from([
-                        higher_cpos_merging(DefaultAlgorithm::RadonFB),
-                        higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
-                    ]),
-                }})
-            },
+                let base_spread = HatConv { radius: Hat1 };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]].into(),
+                        sensor_count: [N_SENSORS_1D],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.06)),
+                        noise_distr: SerializableNormal::new(0.0, cli.variance.unwrap_or(0.2))?,
+                        dataterm: DataTermType::L222,
+                        μ_hat: MU_TRUE_1D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth1D, exponent: Linfinity },
+                        spread: base_spread,
+                        kernel: base_spread,
+                        kernel_plot_width,
+                        noise_seed,
+                        default_merge_radius,
+                        algorithm_overrides: HashMap::from([
+                            higher_cpos_merging(DefaultAlgorithm::RadonFB),
+                            higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
+                        ]),
+                    },
+                })
+            }
             Experiment2D => {
-                let base_spread = Gaussian { variance : Variance1 };
-                let spread_cutoff = BallIndicator { r : CutOff1, exponent : Linfinity };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]; 2].into(),
-                    sensor_count : [N_SENSORS_2D; 2],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.19)),
-                    noise_distr : SerializableNormal::new(0.0, cli.variance.unwrap_or(0.25))?,
-                    dataterm : DataTerm::L2Squared,
-                    μ_hat : MU_TRUE_2D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth2D, exponent : Linfinity },
-                    spread : Prod(spread_cutoff, base_spread),
-                    kernel : Prod(AutoConvolution(spread_cutoff), base_spread),
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::from([
-                        sliding_fb_cut_gaussian,
-                        higher_cpos_merging(DefaultAlgorithm::RadonFB),
-                        higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
-                    ]),
-                }})
-            },
+                let base_spread = Gaussian { variance: Variance1 };
+                let spread_cutoff = BallIndicator { r: CutOff1, exponent: Linfinity };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]; 2].into(),
+                        sensor_count: [N_SENSORS_2D; 2],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.19)),
+                        noise_distr: SerializableNormal::new(0.0, cli.variance.unwrap_or(0.25))?,
+                        dataterm: DataTermType::L222,
+                        μ_hat: MU_TRUE_2D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth2D, exponent: Linfinity },
+                        spread: Prod(spread_cutoff, base_spread),
+                        kernel: Prod(AutoConvolution(spread_cutoff), base_spread),
+                        kernel_plot_width,
+                        noise_seed,
+                        default_merge_radius,
+                        algorithm_overrides: HashMap::from([
+                            sliding_fb_cut_gaussian,
+                            higher_cpos_merging(DefaultAlgorithm::RadonFB),
+                            higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
+                        ]),
+                    },
+                })
+            }
             Experiment2DFast => {
-                let base_spread = HatConv { radius : Hat1 };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]; 2].into(),
-                    sensor_count : [N_SENSORS_2D; 2],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.12)),
-                    noise_distr : SerializableNormal::new(0.0, cli.variance.unwrap_or(0.15))?, //0.25
-                    dataterm : DataTerm::L2Squared,
-                    μ_hat : MU_TRUE_2D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth2D, exponent : Linfinity },
-                    spread : base_spread,
-                    kernel : base_spread,
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::from([
-                        higher_cpos_merging(DefaultAlgorithm::RadonFB),
-                        higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
-                    ]),
-                }})
-            },
-            Experiment1D_L1 => {
-                let base_spread = Gaussian { variance : Variance1 };
-                let spread_cutoff = BallIndicator { r : CutOff1, exponent : Linfinity };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]].into(),
-                    sensor_count : [N_SENSORS_1D],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.1)),
-                    noise_distr : SaltAndPepper::new(
-                        cli.salt_and_pepper.as_ref().map_or(0.6, |v| v[0]),
-                        cli.salt_and_pepper.as_ref().map_or(0.4, |v| v[1])
-                    )?,
-                    dataterm : DataTerm::L1,
-                    μ_hat : MU_TRUE_1D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth1D, exponent : Linfinity },
-                    spread : Prod(spread_cutoff, base_spread),
-                    kernel : Prod(AutoConvolution(spread_cutoff), base_spread),
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::new(),
-                }})
-            },
-            Experiment1D_L1_Fast => {
-                let base_spread = HatConv { radius : Hat1 };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]].into(),
-                    sensor_count : [N_SENSORS_1D],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.12)),
-                    noise_distr : SaltAndPepper::new(
-                        cli.salt_and_pepper.as_ref().map_or(0.6, |v| v[0]),
-                        cli.salt_and_pepper.as_ref().map_or(0.4, |v| v[1])
-                    )?,
-                    dataterm : DataTerm::L1,
-                    μ_hat : MU_TRUE_1D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth1D, exponent : Linfinity },
-                    spread : base_spread,
-                    kernel : base_spread,
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::new(),
-                }})
-            },
-            Experiment2D_L1 => {
-                let base_spread = Gaussian { variance : Variance1 };
-                let spread_cutoff = BallIndicator { r : CutOff1, exponent : Linfinity };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]; 2].into(),
-                    sensor_count : [N_SENSORS_2D; 2],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.35)),
-                    noise_distr : SaltAndPepper::new(
-                        cli.salt_and_pepper.as_ref().map_or(0.8, |v| v[0]),
-                        cli.salt_and_pepper.as_ref().map_or(0.2, |v| v[1])
-                    )?,
-                    dataterm : DataTerm::L1,
-                    μ_hat : MU_TRUE_2D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth2D, exponent : Linfinity },
-                    spread : Prod(spread_cutoff, base_spread),
-                    kernel : Prod(AutoConvolution(spread_cutoff), base_spread),
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::from([
-                    ]),
-                }})
-            },
-            Experiment2D_L1_Fast => {
-                let base_spread = HatConv { radius : Hat1 };
-                Box::new(Named { name, data : ExperimentV2 {
-                    domain : [[0.0, 1.0]; 2].into(),
-                    sensor_count : [N_SENSORS_2D; 2],
-                    regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.40)),
-                    noise_distr : SaltAndPepper::new(
-                        cli.salt_and_pepper.as_ref().map_or(0.8, |v| v[0]),
-                        cli.salt_and_pepper.as_ref().map_or(0.2, |v| v[1])
-                    )?,
-                    dataterm : DataTerm::L1,
-                    μ_hat : MU_TRUE_2D_BASIC.into(),
-                    sensor : BallIndicator { r : SensorWidth2D, exponent : Linfinity },
-                    spread : base_spread,
-                    kernel : base_spread,
-                    kernel_plot_width,
-                    noise_seed,
-                    default_merge_radius,
-                    algorithm_overrides: HashMap::from([
-                    ]),
-                }})
-            },
-            Experiment1D_TV_Fast => {
-                let base_spread = HatConv { radius : HatBias };
-                Box::new(Named { name, data : ExperimentBiased {
-                    λ : 0.02,
-                    bias : MappingSum::new([
-                        Weighted::new(1.0, BallCharacteristic{ center : 0.3.into(), radius : 0.2 }),
-                        Weighted::new(0.5, BallCharacteristic{ center : 0.6.into(), radius : 0.3 }),
-                    ]),
-                    base : ExperimentV2 {
-                        domain : [[0.0, 1.0]].into(),
-                        sensor_count : [N_SENSORS_1D],
-                        regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.2)),
-                        noise_distr : SerializableNormal::new(0.0, cli.variance.unwrap_or(0.1))?,
-                        dataterm : DataTerm::L2Squared,
-                        μ_hat : MU_TRUE_1D_BASIC.into(),
-                        sensor : BallIndicator { r : SensorWidth1D, exponent : Linfinity },
-                        spread : base_spread,
-                        kernel : base_spread,
+                let base_spread = HatConv { radius: Hat1 };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]; 2].into(),
+                        sensor_count: [N_SENSORS_2D; 2],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.12)),
+                        noise_distr: SerializableNormal::new(0.0, cli.variance.unwrap_or(0.15))?, //0.25
+                        dataterm: DataTermType::L222,
+                        μ_hat: MU_TRUE_2D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth2D, exponent: Linfinity },
+                        spread: base_spread,
+                        kernel: base_spread,
                         kernel_plot_width,
                         noise_seed,
                         default_merge_radius,
                         algorithm_overrides: HashMap::from([
-                            higher_cpos_merging_steptune(DefaultAlgorithm::RadonForwardPDPS),
-                            higher_cpos_merging_steptune(DefaultAlgorithm::RadonSlidingPDPS),
+                            higher_cpos_merging(DefaultAlgorithm::RadonFB),
+                            higher_cpos_merging(DefaultAlgorithm::RadonSlidingFB),
                         ]),
                     },
-                }})
-            },
-            Experiment2D_TV_Fast => {
-                let base_spread = HatConv { radius : Hat1 };
-                Box::new(Named { name, data : ExperimentBiased {
-                    λ : 0.005,
-                    bias : MappingSum::new([
-                        Weighted::new(1.0, BallCharacteristic{ center : [0.3, 0.3].into(), radius : 0.2 }),
-                        Weighted::new(0.5, BallCharacteristic{ center : [0.6, 0.6].into(), radius : 0.3 }),
-                    ]),
-                    base : ExperimentV2 {
-                        domain : [[0.0, 1.0]; 2].into(),
-                        sensor_count : [N_SENSORS_2D; 2],
-                        regularisation : Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.06)),
-                        noise_distr : SerializableNormal::new(0.0, cli.variance.unwrap_or(0.15))?, //0.25
-                        dataterm : DataTerm::L2Squared,
-                        μ_hat : MU_TRUE_2D_BASIC.into(),
-                        sensor : BallIndicator { r : SensorWidth2D, exponent : Linfinity },
-                        spread : base_spread,
-                        kernel : base_spread,
+                })
+            }
+            Experiment1D_L1 => {
+                let base_spread = Gaussian { variance: Variance1 };
+                let spread_cutoff = BallIndicator { r: CutOff1, exponent: Linfinity };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]].into(),
+                        sensor_count: [N_SENSORS_1D],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.1)),
+                        noise_distr: SaltAndPepper::new(
+                            cli.salt_and_pepper.as_ref().map_or(0.6, |v| v[0]),
+                            cli.salt_and_pepper.as_ref().map_or(0.4, |v| v[1]),
+                        )?,
+                        dataterm: DataTermType::L1,
+                        μ_hat: MU_TRUE_1D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth1D, exponent: Linfinity },
+                        spread: Prod(spread_cutoff, base_spread),
+                        kernel: Prod(AutoConvolution(spread_cutoff), base_spread),
+                        kernel_plot_width,
+                        noise_seed,
+                        default_merge_radius,
+                        algorithm_overrides: HashMap::new(),
+                    },
+                })
+            }
+            Experiment1D_L1_Fast => {
+                let base_spread = HatConv { radius: Hat1 };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]].into(),
+                        sensor_count: [N_SENSORS_1D],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.12)),
+                        noise_distr: SaltAndPepper::new(
+                            cli.salt_and_pepper.as_ref().map_or(0.6, |v| v[0]),
+                            cli.salt_and_pepper.as_ref().map_or(0.4, |v| v[1]),
+                        )?,
+                        dataterm: DataTermType::L1,
+                        μ_hat: MU_TRUE_1D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth1D, exponent: Linfinity },
+                        spread: base_spread,
+                        kernel: base_spread,
+                        kernel_plot_width,
+                        noise_seed,
+                        default_merge_radius,
+                        algorithm_overrides: HashMap::new(),
+                    },
+                })
+            }
+            Experiment2D_L1 => {
+                let base_spread = Gaussian { variance: Variance1 };
+                let spread_cutoff = BallIndicator { r: CutOff1, exponent: Linfinity };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]; 2].into(),
+                        sensor_count: [N_SENSORS_2D; 2],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.35)),
+                        noise_distr: SaltAndPepper::new(
+                            cli.salt_and_pepper.as_ref().map_or(0.8, |v| v[0]),
+                            cli.salt_and_pepper.as_ref().map_or(0.2, |v| v[1]),
+                        )?,
+                        dataterm: DataTermType::L1,
+                        μ_hat: MU_TRUE_2D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth2D, exponent: Linfinity },
+                        spread: Prod(spread_cutoff, base_spread),
+                        kernel: Prod(AutoConvolution(spread_cutoff), base_spread),
                         kernel_plot_width,
                         noise_seed,
                         default_merge_radius,
-                        algorithm_overrides: HashMap::from([
-                            much_higher_cpos_merging_steptune(DefaultAlgorithm::RadonForwardPDPS),
-                            much_higher_cpos_merging_steptune(DefaultAlgorithm::RadonSlidingPDPS),
+                        algorithm_overrides: HashMap::from([]),
+                    },
+                })
+            }
+            Experiment2D_L1_Fast => {
+                let base_spread = HatConv { radius: Hat1 };
+                Box::new(Named {
+                    name,
+                    data: ExperimentV2 {
+                        domain: [[0.0, 1.0]; 2].into(),
+                        sensor_count: [N_SENSORS_2D; 2],
+                        regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.40)),
+                        noise_distr: SaltAndPepper::new(
+                            cli.salt_and_pepper.as_ref().map_or(0.8, |v| v[0]),
+                            cli.salt_and_pepper.as_ref().map_or(0.2, |v| v[1]),
+                        )?,
+                        dataterm: DataTermType::L1,
+                        μ_hat: MU_TRUE_2D_BASIC.into(),
+                        sensor: BallIndicator { r: SensorWidth2D, exponent: Linfinity },
+                        spread: base_spread,
+                        kernel: base_spread,
+                        kernel_plot_width,
+                        noise_seed,
+                        default_merge_radius,
+                        algorithm_overrides: HashMap::from([]),
+                    },
+                })
+            }
+            Experiment1D_TV_Fast => {
+                let base_spread = HatConv { radius: HatBias };
+                Box::new(Named {
+                    name,
+                    data: ExperimentBiased {
+                        λ: 0.02,
+                        bias: MappingSum::new([
+                            Weighted::new(1.0, BallCharacteristic {
+                                center: 0.3.into(),
+                                radius: 0.2,
+                            }),
+                            Weighted::new(0.5, BallCharacteristic {
+                                center: 0.6.into(),
+                                radius: 0.3,
+                            }),
                         ]),
+                        base: ExperimentV2 {
+                            domain: [[0.0, 1.0]].into(),
+                            sensor_count: [N_SENSORS_1D],
+                            regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.2)),
+                            noise_distr: SerializableNormal::new(0.0, cli.variance.unwrap_or(0.1))?,
+                            dataterm: DataTermType::L222,
+                            μ_hat: MU_TRUE_1D_BASIC.into(),
+                            sensor: BallIndicator { r: SensorWidth1D, exponent: Linfinity },
+                            spread: base_spread,
+                            kernel: base_spread,
+                            kernel_plot_width,
+                            noise_seed,
+                            default_merge_radius,
+                            algorithm_overrides: HashMap::from([
+                                higher_cpos_merging_steptune(DefaultAlgorithm::RadonForwardPDPS),
+                                higher_cpos_merging_steptune(DefaultAlgorithm::RadonSlidingPDPS),
+                            ]),
+                        },
                     },
-                }})
-            },
+                })
+            }
+            Experiment2D_TV_Fast => {
+                let base_spread = HatConv { radius: Hat1 };
+                Box::new(Named {
+                    name,
+                    data: ExperimentBiased {
+                        λ: 0.005,
+                        bias: MappingSum::new([
+                            Weighted::new(1.0, BallCharacteristic {
+                                center: [0.3, 0.3].into(),
+                                radius: 0.2,
+                            }),
+                            Weighted::new(0.5, BallCharacteristic {
+                                center: [0.6, 0.6].into(),
+                                radius: 0.3,
+                            }),
+                        ]),
+                        base: ExperimentV2 {
+                            domain: [[0.0, 1.0]; 2].into(),
+                            sensor_count: [N_SENSORS_2D; 2],
+                            regularisation: Regularisation::NonnegRadon(cli.alpha.unwrap_or(0.06)),
+                            noise_distr: SerializableNormal::new(
+                                0.0,
+                                cli.variance.unwrap_or(0.15),
+                            )?, //0.25
+                            dataterm: DataTermType::L222,
+                            μ_hat: MU_TRUE_2D_BASIC.into(),
+                            sensor: BallIndicator { r: SensorWidth2D, exponent: Linfinity },
+                            spread: base_spread,
+                            kernel: base_spread,
+                            kernel_plot_width,
+                            noise_seed,
+                            default_merge_radius,
+                            algorithm_overrides: HashMap::from([
+                                much_higher_cpos_merging_steptune(
+                                    DefaultAlgorithm::RadonForwardPDPS,
+                                ),
+                                much_higher_cpos_merging_steptune(
+                                    DefaultAlgorithm::RadonSlidingPDPS,
+                                ),
+                            ]),
+                        },
+                    },
+                })
+            }
         })
     }
 }
-

mercurial