--- a/src/run.rs Fri May 15 14:40:02 2026 -0500 +++ b/src/run.rs Sun Jul 19 07:34:39 2026 +0200 @@ -23,7 +23,9 @@ }; use crate::regularisation::{NonnegRadonRegTerm, RadonRegTerm, Regularisation, SlidingRegTerm}; use crate::seminorms::*; -use crate::sliding_fb::{pointsource_sliding_fb_reg, SlidingFBConfig, TransportConfig}; +use crate::sliding_fb::{ + pointsource_sliding_fb_reg, SlidingFBConfig, TransportConfig, TransportProxPenalty, +}; use crate::sliding_pdps::{ pointsource_sliding_fb_pair, pointsource_sliding_pdps_pair, SlidingPDPSConfig, }; @@ -129,8 +131,14 @@ }; let override_transport = |g: TransportConfig<F>| TransportConfig { θ0: cli.theta0.unwrap_or(g.θ0), - tolerance_mult_con: cli.transport_tolerance_pos.unwrap_or(g.tolerance_mult_con), + τθ: cli.tautheta.or(g.τθ), + tolerance_mult: cli.transport_tolerance.unwrap_or(g.tolerance_mult), + ℓ_gradv_mult: cli.gradv_lipest_mult.unwrap_or(g.ℓ_gradv_mult), adaptation: cli.transport_adaptation.unwrap_or(g.adaptation), + allow_partial_transport: cli + .allow_partial_transport + .unwrap_or(g.allow_partial_transport), + alt_remainder_control: cli.alt_remainder_control.unwrap_or(g.alt_remainder_control), ..g }; @@ -270,12 +278,20 @@ impl DefaultAlgorithm { /// Returns the algorithm configuration corresponding to the algorithm shorthand - pub fn default_config<F: Float>(&self) -> AlgorithmConfig<F> { + pub fn default_config<F: Float>( + &self, + regularisation_hint: Option<&Regularisation<F>>, + ) -> AlgorithmConfig<F> { use DefaultAlgorithm::*; let radon_insertion = InsertionConfig { - merging: SpikeMergingMethod { interp: false, ..Default::default() }, + merging: SpikeMergingMethod { enabled: true, interp: false, ..Default::default() }, + fitness_merging: true, inner: InnerSettings { - method: InnerMethod::PDPS, // SSN not implemented + method: if let Some(Regularisation::NonnegRadon(_)) = regularisation_hint { + InnerMethod::Exact + } else { + InnerMethod::PDPS // SSN not implemented + }, ..Default::default() }, ..Default::default() @@ -320,11 +336,6 @@ } } - /// Returns the [`Named`] algorithm corresponding to the algorithm shorthand - pub fn get_named<F: Float>(&self) -> Named<AlgorithmConfig<F>> { - self.to_named(self.default_config()) - } - pub fn to_named<F: Float>(self, alg: AlgorithmConfig<F>) -> Named<AlgorithmConfig<F>> { Named { name: self.name(), data: alg } } @@ -485,12 +496,9 @@ fn runall( &self, cli: &CommandLineArgs, - algs: Option<Vec<Named<AlgorithmConfig<F>>>>, + cli_algorithm_overrides: &AlgorithmOverrides<F>, ) -> DynError; - /// Return algorithm default config - fn algorithm_overrides(&self, alg: DefaultAlgorithm) -> AlgorithmOverrides<F>; - /// Experiment name fn name(&self) -> &str; } @@ -511,6 +519,7 @@ TimingIteratorFactory<BasicAlgIteratorFactory<IterInfo<F>>>, >; +/// Trait for things used by many a [`RunnableExperiment`], but that are not dyn-compatible. pub trait RunnableExperimentExtras<F: ClapFloat>: RunnableExperiment<F> + Serialize + Sized { @@ -545,6 +554,42 @@ Ok(prefix) } + /// Collect algorithms to run based on command line args. + fn collect_algs( + &self, + cli: &CommandLineArgs, + algorithm_overrides: &AlgorithmOverrides<F>, + algorithm_overrides_fn: impl Fn(DefaultAlgorithm) -> Option<AlgorithmOverrides<F>>, + regularisation_hint: Option<&Regularisation<F>>, + dflt: &[DefaultAlgorithm], + ) -> DynResult<Vec<Named<AlgorithmConfig<F>>>> + where + F: for<'a> Deserialize<'a>, + { + let proc_alg = |alg: &DefaultAlgorithm| { + let default_cfg = alg.default_config(regularisation_hint); + let cfg = if let Some(over) = &algorithm_overrides_fn(*alg) { + default_cfg.cli_override(over) + } else { + default_cfg + } + .cli_override(algorithm_overrides); + alg.to_named(cfg) + }; + + let mut algs: Vec<Named<AlgorithmConfig<F>>> = cli.algorithm.iter().map(proc_alg).collect(); + for filename in cli.saved_algorithm.iter() { + let f = std::fs::File::open(filename)?; + let alg = serde_json::from_reader(f)?; + algs.push(alg); + } + if algs.is_empty() { + Ok(dflt.iter().map(proc_alg).collect()) + } else { + Ok(algs) + } + } + /// Helper function to run all algorithms on an experiment. fn do_runall<P, Z, Plot, const N: usize>( &self, @@ -707,22 +752,10 @@ self.name.as_ref() } - fn algorithm_overrides(&self, alg: DefaultAlgorithm) -> AlgorithmOverrides<F> { - AlgorithmOverrides { - merge_radius: Some(self.data.default_merge_radius), - ..self - .data - .algorithm_overrides - .get(&alg) - .cloned() - .unwrap_or(Default::default()) - } - } - fn runall( &self, cli: &CommandLineArgs, - algs: Option<Vec<Named<AlgorithmConfig<F>>>>, + cli_algorithm_overrides: &AlgorithmOverrides<F>, ) -> DynError { // Get experiment configuration let &ExperimentV2 { @@ -741,11 +774,34 @@ } = &self.data; // Set up algorithms - let algorithms = match (algs, dataterm) { - (Some(algs), _) => algs, - (None, DataTermType::L222) => vec![DefaultAlgorithm::FB.get_named()], - (None, DataTermType::L1) => vec![DefaultAlgorithm::PDPS.get_named()], - }; + let algorithms = self.collect_algs( + cli, + cli_algorithm_overrides, + |alg| { + Some(AlgorithmOverrides { + merge_radius: Some(self.data.default_merge_radius), + ..self + .data + .algorithm_overrides + .get(&alg) + .cloned() + .unwrap_or_else(Default::default) + }) + }, + Some(®ularisation), + match dataterm { + DataTermType::L222 => &[ + DefaultAlgorithm::FB, + DefaultAlgorithm::RadonFB, + DefaultAlgorithm::SlidingFB, + DefaultAlgorithm::RadonSlidingFB, + DefaultAlgorithm::FW, + DefaultAlgorithm::FWRelax, + DefaultAlgorithm::PDPS, + ], + DataTermType::L1 => &[DefaultAlgorithm::PDPS], + }, + )?; // Set up operators let depth = DynamicDepth(8); @@ -944,10 +1000,11 @@ F: Float + ToNalgebraRealField, I: AlgIteratorFactory<IterInfo<F>>, Dat: DifferentiableMapping<RNDM<N, F>, Codomain = F> + BoundedCurvature<F>, - Dat::DerivativeDomain: DifferentiableRealMapping<N, F> + ClosedMul<F>, + Dat::DerivativeDomain: + DifferentiableRealMapping<N, F> + ClosedMul<F> + MinMaxMapping<Loc<N, F>, F>, RNDM<N, F>: SpikeMerging<F>, Reg: SlidingRegTerm<Loc<N, F>, F>, - P: ProxPenalty<Loc<N, F>, Dat::DerivativeDomain, Reg, F> + StepLengthBound<F, Dat>, + P: TransportProxPenalty<Loc<N, F>, Dat::DerivativeDomain, Reg, F> + StepLengthBound<F, Dat>, Plot: Plotter<P::ReturnMapping, Dat::DerivativeDomain, RNDM<N, F>>, { let pt = P::prox_type(); @@ -1067,23 +1124,10 @@ self.name.as_ref() } - fn algorithm_overrides(&self, alg: DefaultAlgorithm) -> AlgorithmOverrides<F> { - AlgorithmOverrides { - merge_radius: Some(self.data.base.default_merge_radius), - ..self - .data - .base - .algorithm_overrides - .get(&alg) - .cloned() - .unwrap_or(Default::default()) - } - } - fn runall( &self, cli: &CommandLineArgs, - algs: Option<Vec<Named<AlgorithmConfig<F>>>>, + cli_algorithm_overrides: &AlgorithmOverrides<F>, ) -> DynError { // Get experiment configuration let &ExperimentBiased { @@ -1107,10 +1151,29 @@ } = &self.data; // Set up algorithms - let algorithms = match (algs, dataterm) { - (Some(algs), _) => algs, - _ => vec![DefaultAlgorithm::SlidingPDPS.get_named()], - }; + let algorithms = self.collect_algs( + cli, + cli_algorithm_overrides, + |alg| { + Some(AlgorithmOverrides { + merge_radius: Some(self.data.base.default_merge_radius), + ..self + .data + .base + .algorithm_overrides + .get(&alg) + .cloned() + .unwrap_or_else(Default::default) + }) + }, + Some(®ularisation), + &[ + DefaultAlgorithm::SlidingPDPS, + DefaultAlgorithm::ForwardPDPS, + DefaultAlgorithm::RadonSlidingPDPS, + DefaultAlgorithm::RadonForwardPDPS, + ], + )?; // Set up operators let depth = DynamicDepth(8); @@ -1259,19 +1322,19 @@ I: AlgIteratorFactory<IterInfo<F>>, Dat: DifferentiableMapping<MeasureZ<F, Z, N>, Codomain = F, DerivativeDomain = Pair<S, Z>> + BoundedCurvature<F>, - S: DifferentiableRealMapping<N, F> + ClosedMul<F>, + S: DifferentiableRealMapping<N, F> + ClosedMul<F> + MinMaxMapping<Loc<N, F>, F>, for<'a> Pair<&'a P, &'a IdOp<Z>>: StepLengthBoundPair<F, Dat>, //Pair<S, Z>: ClosedMul<F>, RNDM<N, F>: SpikeMerging<F>, Reg: SlidingRegTerm<Loc<N, F>, F>, - P: ProxPenalty<Loc<N, F>, S, Reg, F>, + P: TransportProxPenalty<Loc<N, F>, S, Reg, F>, KOpZ: BoundedLinear<Z, L2, L2, F, Codomain = Y> + GEMV<F, Z> + SimplyAdjointable<Z, Y, AdjointCodomain = Z>, KOpZ::SimpleAdjoint: GEMV<F, Y>, Y: ClosedEuclidean<F> + Clone, for<'b> &'b Y: Instance<Y>, - Z: ClosedEuclidean<F> + Clone + ClosedMul<F>, + Z: ClosedEuclidean<F> + Clone + ClosedMul<F> + AXPY, for<'b> &'b Z: Instance<Z>, R: Prox<Z, Codomain = F>, H: Conjugable<Y, F, Codomain = F>, @@ -1342,10 +1405,10 @@ I: AlgIteratorFactory<IterInfo<F>>, Dat: DifferentiableMapping<MeasureZ<F, Z, N>, Codomain = F, DerivativeDomain = Pair<S, Z>> + BoundedCurvature<F>, - S: DifferentiableRealMapping<N, F> + ClosedMul<F>, + S: DifferentiableRealMapping<N, F> + ClosedMul<F> + MinMaxMapping<Loc<N, F>, F>, RNDM<N, F>: SpikeMerging<F>, Reg: SlidingRegTerm<Loc<N, F>, F>, - P: ProxPenalty<Loc<N, F>, S, Reg, F>, + P: TransportProxPenalty<Loc<N, F>, S, Reg, F>, for<'a> Pair<&'a P, &'a IdOp<Z>>: StepLengthBoundPair<F, Dat>, Z: ClosedEuclidean<F> + AXPY + Clone, for<'b> &'b Z: Instance<Z>,