Tue, 04 Aug 2026 14:12:18 -0500
Add AdmissibleDomain
| Cargo.lock | file | annotate | diff | comparison | revisions | |
| Cargo.toml | file | annotate | diff | comparison | revisions | |
| src/domain.rs | file | annotate | diff | comparison | revisions | |
| src/fb.rs | file | annotate | diff | comparison | revisions | |
| src/forward_pdps.rs | file | annotate | diff | comparison | revisions | |
| src/lib.rs | file | annotate | diff | comparison | revisions | |
| src/pdps.rs | file | annotate | diff | comparison | revisions | |
| src/prox_penalty.rs | file | annotate | diff | comparison | revisions | |
| src/regularisation.rs | file | annotate | diff | comparison | revisions | |
| src/run.rs | file | annotate | diff | comparison | revisions | |
| src/sliding_fb.rs | file | annotate | diff | comparison | revisions | |
| src/sliding_pdps.rs | file | annotate | diff | comparison | revisions |
--- a/Cargo.lock Tue Aug 04 13:46:29 2026 -0500 +++ b/Cargo.lock Tue Aug 04 14:12:18 2026 -0500 @@ -878,9 +878,9 @@ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha", "rand_core",
--- a/Cargo.toml Tue Aug 04 13:46:29 2026 -0500 +++ b/Cargo.toml Tue Aug 04 14:12:18 2026 -0500 @@ -34,7 +34,7 @@ [dependencies] serde = { version = "1.0", features = ["derive"] } num-traits = { version = "~0.2.14", features = ["std"] } -rand = "~0.9.2" +rand = "~0.9.5" colored = "~2.1.0" rand_distr = "~0.5.1" nalgebra = { version = "~0.34.0", features = ["rand-no-std"] }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/domain.rs Tue Aug 04 14:12:18 2026 -0500 @@ -0,0 +1,46 @@ +/*! +Definition of an admissible domain. +*/ + +use alg_tools::instance::{Instance, Space}; +use alg_tools::loc::Loc; +use alg_tools::sets::{Cube, Set}; +use alg_tools::types::Num; +use rand::distr::Uniform; +use rand::prelude::{Rng, StdRng}; + +/// Admissible domain +pub trait AdmissibleDomain<Point: Space>: Set<Point> { + // Generate a random point in the domain + fn random_point(&mut self) -> Option<Point> { + None + } +} + +impl<F: Num + PartialOrd + Sized, const N: usize> AdmissibleDomain<Loc<N, F>> for Cube<N, F> {} + +/// +pub struct FullSpace; + +impl<A: Space> Set<A> for FullSpace { + fn contains<I: Instance<A>>(&self, _: I) -> bool { + true + } +} + +impl<F: Num + PartialOrd + Sized, const N: usize> AdmissibleDomain<Loc<N, F>> for FullSpace {} + +pub struct UniformRandom<S, R: Rng = StdRng>(pub S, pub R); + +impl<A: Space, S: Set<A>, R: Rng> Set<A> for UniformRandom<S, R> { + fn contains<I: Instance<A>>(&self, item: I) -> bool { + self.0.contains(item) + } +} + +impl<const N: usize, R: Rng> AdmissibleDomain<Loc<N, f64>> for UniformRandom<Cube<N, f64>, R> { + fn random_point(&mut self) -> Option<Loc<N, f64>> { + let UniformRandom(ref cube, ref mut rng) = *self; + Some(Loc(cube.map(|a, b| rng.sample(Uniform::new(a, b).unwrap())))) + } +}
--- a/src/fb.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/fb.rs Tue Aug 04 14:12:18 2026 -0500 @@ -77,6 +77,7 @@ [`crate::subproblem::InnerSettings`] in [`InsertionConfig::inner`]. */ +use crate::domain::{AdmissibleDomain, FullSpace}; use crate::measures::merging::SpikeMerging; use crate::measures::{DiscreteMeasure, RNDM}; use crate::plot::Plotter; @@ -89,6 +90,7 @@ use alg_tools::mapping::DifferentiableMapping; use alg_tools::nalgebra_support::ToNalgebraRealField; use colored::Colorize; +use measures::DeltaMeasure; use numeric_literals::replace_float_literals; use serde::{Deserialize, Serialize}; @@ -146,7 +148,7 @@ /// /// Returns the final iterate. #[replace_float_literals(F::cast_from(literal))] -pub fn pointsource_fb_reg<F, I, Dat, Reg, P, Plot, const N: usize>( +pub fn pointsource_fb_dom<F, I, Dat, Reg, P, Plot, const N: usize>( f: &Dat, reg: &Reg, prox_penalty: &P, @@ -154,6 +156,7 @@ iterator: I, mut plotter: Plot, μ0: Option<RNDM<N, F>>, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, ) -> DynResult<RNDM<N, F>> where F: Float + ToNalgebraRealField, @@ -188,7 +191,7 @@ // Run the algorithm for state in iterator.iter_init(|| full_stats(&μ, ε, stats.clone())) { - let maybe_μ_base = config.if_merge_now(&state, |_| μ.clone()); + let maybe_μ_base = config.if_merge_now_check(&state, || μ.clone()); let μ_base_len = μ.len(); // Calculate smooth part of surrogate model. @@ -200,7 +203,10 @@ stats.inserted += μ.len() - μ_base_len; // Prune and possibly merge spikes - if let Some(μ_base) = maybe_μ_base { + let did_merge = if let Some(μ_base) = maybe_μ_base { + // Merge spikes. + // This crucially expects the merge routine to be stable with respect to spike locations, + // and not to performing any pruning. That is be to done below simultaneously for γ. config.if_merge_now(&state, |ins| { stats.merged += prox_penalty.merge_spikes( &mut μ, @@ -211,19 +217,29 @@ ins, ®, Some(|μ̃: &RNDM<N, F>| f.apply(μ̃) + reg.apply(μ̃)), - ) - }); - } + ); + stats.pruned += prune_with_stats(&mut μ); - stats.pruned += prune_with_stats(&mut μ); + if config.random_insertions { + if let Some(x) = domain.random_point() { + μ += DeltaMeasure { x, α: 0.0 }; + stats.inserted += 1; + } + } - // Do extra weight optimisation step heuristic - for _ in 0..config.extra_weight_optimisation_steps { - τv = f.differential(&μ) * τ; - prox_penalty.reweigh(&mut μ, &mut τv, τ, ε, config, ®, &state, &mut stats)?; - } + // Do extra weight optimisation step heuristic + for _ in 1..config.extra_weight_optimisation_steps { + τv = f.differential(&μ) * τ; + prox_penalty + .reweigh(&mut μ, &mut τv, τ, ε, &config, ®, &state, &mut stats)?; + } + Ok(()) + })? + } else { + None + }; - if config.extra_weight_optimisation_steps > 0 { + if did_merge.is_none() { stats.pruned += prune_with_stats(&mut μ); } @@ -244,6 +260,38 @@ postprocess(μ, config, |μ̃| f.apply(μ̃) + reg.apply(μ̃)) } +#[deprecated] +pub fn pointsource_fb_reg<F, I, Dat, Reg, P, Plot, const N: usize>( + f: &Dat, + reg: &Reg, + prox_penalty: &P, + fbconfig: &FBConfig<F>, + iterator: I, + plotter: Plot, + μ0: Option<RNDM<N, F>>, +) -> DynResult<RNDM<N, F>> +where + F: Float + ToNalgebraRealField, + I: AlgIteratorFactory<IterInfo<F>>, + RNDM<N, F>: SpikeMerging<F>, + Dat: DifferentiableMapping<RNDM<N, F>, Codomain = F>, + Dat::DerivativeDomain: ClosedMul<F>, + Reg: RegTerm<Loc<N, F>, F>, + P: ProxPenalty<Loc<N, F>, Dat::DerivativeDomain, Reg, F> + StepLengthBound<F, Dat>, + Plot: Plotter<P::ReturnMapping, Dat::DerivativeDomain, RNDM<N, F>>, +{ + pointsource_fb_dom( + f, + reg, + prox_penalty, + fbconfig, + iterator, + plotter, + μ0, + &mut FullSpace, + ) +} + /// Iteratively solve the pointsource localisation problem using inertial forward-backward splitting. /// /// The settings in `config` have their [respective documentation](FBConfig). `opA` is the @@ -312,7 +360,7 @@ // (Do not) merge spikes. if !warned_merging { - config.if_merge_now(&state, |_| { + config.if_merge_now_check(&state, || { let err = format!("Merging not supported for μFISTA"); println!("{}", err.red()); warned_merging = true;
--- a/src/forward_pdps.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/forward_pdps.rs Tue Aug 04 14:12:18 2026 -0500 @@ -3,6 +3,7 @@ primal-dual proximal splitting with a forward step. */ +use crate::domain::{AdmissibleDomain, FullSpace}; use crate::fb::*; use crate::measures::merging::SpikeMerging; use crate::measures::{DiscreteMeasure, RNDM}; @@ -20,6 +21,7 @@ use alg_tools::nalgebra_support::ToNalgebraRealField; use alg_tools::norms::L2; use anyhow::ensure; +use measures::DeltaMeasure; use numeric_literals::replace_float_literals; use serde::{Deserialize, Serialize}; @@ -85,7 +87,7 @@ /// τ = τ_0 \frac{1 - σ_{p,0}}{(σ_d M (1-σ_p L_z) + (1 - σ_{p,0} L)}. /// $$ #[replace_float_literals(F::cast_from(literal))] -pub fn pointsource_forward_pdps_pair< +pub fn pointsource_forward_pdps_pair_dom< F, I, S, @@ -111,6 +113,7 @@ opKz: &KOpZ, fnR: &R, fnH: &H, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, ) -> DynResult<MeasureZ<F, Z, N>> where F: Float + ToNalgebraRealField, @@ -237,10 +240,7 @@ // Merge spikes. // This crucially expects the merge routine to be stable with respect to spike locations, // and not to performing any pruning. That is be to done below simultaneously for γ. - // Merge spikes. - // This crucially expects the merge routine to be stable with respect to spike locations, - // and not to performing any pruning. That is be to done below simultaneously for γ. - config.insertion.if_merge_now(&state, |ins| { + let did_merge = config.insertion.if_merge_now(&state, |ins| { stats.merged += prox_penalty.merge_spikes( &mut μ, &mut τv, @@ -249,32 +249,45 @@ ε, ins, ®, - Some(|μ̃: &RNDM<N, F>| f.apply(Pair(μ̃, &z))+ reg.apply(μ̃)), + Some(|μ̃: &RNDM<N, F>| f.apply(Pair(μ̃, &z)) + reg.apply(μ̃)), ); - }); + stats.pruned += prune_with_stats(&mut μ); - // Prune spikes with zero weight. - stats.pruned += prune_with_stats(&mut μ); + if config.insertion.random_insertions { + if let Some(x) = domain.random_point() { + μ += DeltaMeasure { x, α: 0.0 }; + stats.inserted += 1; + } + } - for _ in 0..config.insertion.extra_weight_optimisation_steps { - // Do extra weight optimisation step heuristic - let Pair(v, _z_tmp) = f.differential(Pair(&μ, &z)); - τv = v * τ; - prox_penalty.reweigh( - &mut μ, - &mut τv, - τ, - ε, - &config.insertion, - ®, - &state, - &mut stats, - )?; - //(z, y) = zy_step(z, y, z_tmp); - } + for _ in 0..config.insertion.extra_weight_optimisation_steps { + // Do extra weight optimisation step heuristic + let Pair(v, mut _z_tmp) = f.differential(Pair(&μ, &z)); + τv = v * τ; + prox_penalty.reweigh( + &mut μ, + &mut τv, + τ, + ε, + &config.insertion, + ®, + &state, + &mut stats, + )?; - if config.insertion.extra_weight_optimisation_steps > 0 { - // Prune spikes with zero weight. + // opKz_adj.apply_add(&mut z_tmp, &y); + // z_tmp.axpy(1.0, &z, -σ_p); + // let z_new = fnR.prox(σ_p, z_tmp); + // z.axpy(1.0 + ω, &z_new, -ω); + // //z = (z - &z_new) * (-ω) + &z_new; + // opKz.gemv(&mut y, σ_d, z, 1.0); + // y = starH.prox(σ_d, y); + // z = z_new; + } + Ok(()) + })?; + + if did_merge.is_none() { stats.pruned += prune_with_stats(&mut μ); } @@ -305,13 +318,79 @@ Ok(Pair(μ, z)) } +#[deprecated] +pub fn pointsource_forward_pdps_pair< + F, + I, + S, + Dat, + Reg, + P, + Z, + R, + Y, + /*KOpM, */ KOpZ, + H, + Plot, + const N: usize, +>( + f: &Dat, + reg: &Reg, + prox_penalty: &P, + config: &ForwardPDPSConfig<F>, + iterator: I, + plotter: Plot, + init: (Option<RNDM<N, F>>, Z, Y), + //opKμ : KOpM, + opKz: &KOpZ, + fnR: &R, + fnH: &H, +) -> DynResult<MeasureZ<F, Z, N>> +where + F: Float + ToNalgebraRealField, + I: AlgIteratorFactory<IterInfo<F>>, + Dat: DifferentiableMapping<MeasureZ<F, Z, N>, Codomain = F, DerivativeDomain = Pair<S, Z>>, + //Pair<S, Z>: ClosedMul<F>, // Doesn't really need to be closed, if make this signature more complex… + S: DifferentiableRealMapping<N, F> + ClosedMul<F>, + RNDM<N, F>: SpikeMerging<F>, + Reg: RegTerm<Loc<N, F>, F>, + P: ProxPenalty<Loc<N, F>, S, Reg, F>, + for<'a> Pair<&'a P, &'a IdOp<Z>>: StepLengthBoundPair<F, Dat>, + KOpZ: BoundedLinear<Z, L2, L2, F, Codomain = Y> + + GEMV<F, Z, Y> + + SimplyAdjointable<Z, Y, Codomain = Y, AdjointCodomain = Z>, + KOpZ::SimpleAdjoint: GEMV<F, Y, Z>, + Y: ClosedEuclidean<F>, + for<'b> &'b Y: Instance<Y>, + Z: ClosedEuclidean<F> + AXPY, + for<'b> &'b Z: Instance<Z>, + R: Prox<Z, Codomain = F>, + H: Conjugable<Y, F, Codomain = F>, + for<'b> H::Conjugate<'b>: Prox<Y>, + Plot: Plotter<P::ReturnMapping, S, RNDM<N, F>>, +{ + pointsource_forward_pdps_pair_dom( + f, + reg, + prox_penalty, + config, + iterator, + plotter, + init, + opKz, + fnR, + fnH, + &mut FullSpace, + ) +} + /// Iteratively solve the pointsource localisation with an additional variable /// using forward-backward splitting. /// /// The implementation uses [`pointsource_forward_pdps_pair`] with appropriate dummy /// variables, operators, and functions. #[replace_float_literals(F::cast_from(literal))] -pub fn pointsource_fb_pair<F, I, S, Dat, Reg, P, Z, R, Plot, const N: usize>( +pub fn pointsource_fb_pair_dom<F, I, S, Dat, Reg, P, Z, R, Plot, const N: usize>( f: &Dat, reg: &Reg, prox_penalty: &P, @@ -321,6 +400,7 @@ (μ0, z): (Option<RNDM<N, F>>, Z), //opKμ : KOpM, fnR: &R, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, ) -> DynResult<MeasureZ<F, Z, N>> where F: Float + ToNalgebraRealField, @@ -345,7 +425,7 @@ let &FBConfig { τ0, σp0, insertion } = config; let pdps_config = ForwardPDPSConfig { τ0, σp0, insertion, σd0: 0.0 }; - pointsource_forward_pdps_pair( + pointsource_forward_pdps_pair_dom( f, reg, prox_penalty, @@ -356,5 +436,48 @@ &opKz, fnR, &fnH, + domain, ) } + +#[deprecated] +#[replace_float_literals(F::cast_from(literal))] +pub fn pointsource_fb_pair<F, I, S, Dat, Reg, P, Z, R, Plot, const N: usize>( + f: &Dat, + reg: &Reg, + prox_penalty: &P, + config: &FBConfig<F>, + iterator: I, + plotter: Plot, + init: (Option<RNDM<N, F>>, Z), + //opKμ : KOpM, + fnR: &R, +) -> DynResult<MeasureZ<F, Z, N>> +where + F: Float + ToNalgebraRealField, + I: AlgIteratorFactory<IterInfo<F>>, + Dat: DifferentiableMapping<MeasureZ<F, Z, N>, Codomain = F, DerivativeDomain = Pair<S, Z>>, + S: DifferentiableRealMapping<N, F> + ClosedMul<F>, + RNDM<N, F>: SpikeMerging<F>, + Reg: RegTerm<Loc<N, F>, F>, + P: ProxPenalty<Loc<N, F>, S, Reg, F>, + for<'a> Pair<&'a P, &'a IdOp<Z>>: StepLengthBoundPair<F, Dat>, + Z: ClosedEuclidean<F> + AXPY<Field = F> + Clone, + for<'b> &'b Z: Instance<Z>, + R: Prox<Z, Codomain = F>, + Plot: Plotter<P::ReturnMapping, S, RNDM<N, F>>, + // We should not need to explicitly require this: + for<'b> &'b Loc<0, F>: Instance<Loc<0, F>>, +{ + pointsource_fb_pair_dom( + f, + reg, + prox_penalty, + config, + iterator, + plotter, + init, + fnR, + &mut FullSpace, + ) +}
--- a/src/lib.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/lib.rs Tue Aug 04 14:12:18 2026 -0500 @@ -21,6 +21,7 @@ //use pyo3::pyclass; pub mod dataterm; +pub mod domain; pub mod fb; pub mod forward_model; pub mod forward_pdps; @@ -242,9 +243,14 @@ pub max_scaling_iter: Option<usize>, #[arg(long)] - /// Extra finite dimensional steps to take + /// Extra finite dimensional steps to take after merge and random insertion. + /// This all methods support this. pub extra_weight_optimisation_steps: Option<usize>, + #[arg(long)] + /// Whether to perform random insertions after merge. Not all methods support this. + pub random_insertions: Option<bool>, + #[arg(long, value_names = &["ε", "θ", "p"])] /// Set the tolerance to ε_k = ε/(1+θk)^p pub tolerance: Option<Vec<F>>,
--- a/src/pdps.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/pdps.rs Tue Aug 04 14:12:18 2026 -0500 @@ -212,7 +212,8 @@ config.if_merge_now(&state, |ins| { stats.merged += prox_penalty.merge_spikes_no_fitness(&mut μ, &mut τv, &μ_base, τ, ε, ins, ®); - }); + Ok(()) + })?; stats.inserted += μ.len() - μ_base.len(); stats.pruned += prune_with_stats(&mut μ);
--- a/src/prox_penalty.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/prox_penalty.rs Tue Aug 04 14:12:18 2026 -0500 @@ -66,9 +66,12 @@ /// Iterations between merging heuristic tries pub merge_every: usize, - /// Additional weight optimisation steps + /// Additional weight optimisation steps after merge and random insertion pub extra_weight_optimisation_steps: usize, + /// Random insertion after merge + pub random_insertions: bool, + /// Maximum iteration for scaling hack, zero if no maximum. pub max_scaling_iter: usize, } @@ -91,16 +94,45 @@ merge_tolerance_mult: 2.0, extra_weight_optimisation_steps: 0, max_scaling_iter: 0, + random_insertions: false, } } } impl<F: Float> InsertionConfig<F> { - /// Check if merging should be attempted this iteration + /// Check if merging should be attempted this iteration. + /// Wrapped in a possible error, returns [`Some`] of the return value of `f`, if merging should + /// be performed. pub fn if_merge_now<I: AlgIterator, A>( &self, state: &AlgIteratorIteration<I>, - mut f: impl FnMut(&Self) -> A, + mut f: impl FnMut(&Self) -> DynResult<A>, + ) -> DynResult<Option<A>> { + if (self.merging.enabled || self.merging.scaling.is_some()) + && state.iteration() % self.merge_every == 0 + { + if self.max_scaling_iter != 0 && state.iteration() > self.max_scaling_iter { + if !self.merging.enabled { + Ok(None) + } else { + Ok(Some(f(&InsertionConfig { + merging: SpikeMergingMethod { scaling: None, ..self.merging }, + ..*self + })?)) + } + } else { + Ok(Some(f(self)?)) + } + } else { + Ok(None) + } + } + + // Lightweight version of [`Self::if_merge_now`]. + pub fn if_merge_now_check<I: AlgIterator, A>( + &self, + state: &AlgIteratorIteration<I>, + mut f: impl FnMut() -> A, ) -> Option<A> { if (self.merging.enabled || self.merging.scaling.is_some()) && state.iteration() % self.merge_every == 0 @@ -109,13 +141,10 @@ if !self.merging.enabled { None } else { - Some(f(&InsertionConfig { - merging: SpikeMergingMethod { scaling: None, ..self.merging }, - ..*self - })) + Some(f()) } } else { - Some(f(self)) + Some(f()) } } else { None
--- a/src/regularisation.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/regularisation.rs Tue Aug 04 14:12:18 2026 -0500 @@ -3,11 +3,11 @@ */ #[allow(unused_imports)] // Used by documentation. -use crate::fb::pointsource_fb_reg; +use crate::fb::pointsource_fb_dom; use crate::fb::InsertionConfig; use crate::measures::{DeltaMeasure, DiscreteMeasure, Radon, RNDM}; #[allow(unused_imports)] // Used by documentation. -use crate::sliding_fb::pointsource_sliding_fb_dom; +use crate::sliding_fb::pointsource_sliding_fb; use crate::types::*; use alg_tools::instance::{Instance, Space}; use alg_tools::linops::Mapping; @@ -27,7 +27,7 @@ use std::cmp::Ordering::{Equal, Greater, Less}; -/// The regularisation term $α\\|μ\\|\_{ℳ(Ω)} + δ_{≥ 0}(μ)$ for [`pointsource_fb_reg`] and other +/// The regularisation term $α\\|μ\\|\_{ℳ(Ω)} + δ_{≥ 0}(μ)$ for [`pointsource_fb_dom`] and other /// algorithms. /// /// The only member of the struct is the regularisation parameter α.
--- a/src/run.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/run.rs Tue Aug 04 14:12:18 2026 -0500 @@ -2,7 +2,8 @@ This module provides [`RunnableExperiment`] for running chosen algorithms on a chosen experiment. */ -use crate::fb::{pointsource_fb_reg, pointsource_fista_reg, FBConfig, InsertionConfig}; +use crate::domain::{AdmissibleDomain, FullSpace}; +use crate::fb::{pointsource_fb_dom, pointsource_fista_reg, FBConfig, InsertionConfig}; use crate::forward_model::sensor_grid::{ //SensorGridBTFN, Sensor, @@ -11,7 +12,9 @@ Spread, }; use crate::forward_model::*; -use crate::forward_pdps::{pointsource_fb_pair, pointsource_forward_pdps_pair, ForwardPDPSConfig}; +use crate::forward_pdps::{ + pointsource_fb_pair_dom, pointsource_forward_pdps_pair_dom, ForwardPDPSConfig, +}; use crate::frank_wolfe::{pointsource_fw_reg, FWConfig, FWVariant, RegTermFW}; use crate::kernels::*; use crate::measures::merging::{SpikeMerging, SpikeMergingMethod}; @@ -24,7 +27,7 @@ use crate::regularisation::{NonnegRadonRegTerm, RadonRegTerm, Regularisation, SlidingRegTerm}; use crate::seminorms::*; use crate::sliding_fb::{ - pointsource_sliding_fb_dom, SlidingFBConfig, TransportConfig, TransportProxPenalty, + pointsource_sliding_fb, SlidingFBConfig, TransportConfig, TransportProxPenalty, }; use crate::sliding_pdps::{ pointsource_sliding_fb_pair_dom, pointsource_sliding_pdps_pair_dom, SlidingPDPSConfig, @@ -127,6 +130,7 @@ extra_weight_optimisation_steps: cli .extra_weight_optimisation_steps .unwrap_or(g.extra_weight_optimisation_steps), + random_insertions: cli.random_insertions.unwrap_or(g.random_insertions), tolerance: cli .tolerance .as_ref() @@ -989,13 +993,7 @@ f: &Dat, reg: &Reg, prox_penalty: &P, - init: ( - &AlgorithmConfig<F>, - I, - Plot, - Option<RNDM<N, F>>, - String, - ), + init: (&AlgorithmConfig<F>, I, Plot, Option<RNDM<N, F>>, String), cont: impl FnOnce( (&AlgorithmConfig<F>, I, Plot, Option<RNDM<N, F>>, String), ) -> DynResult<RNDM<N, F>>, @@ -1011,7 +1009,7 @@ P: TransportProxPenalty<Loc<N, F>, Dat::DerivativeDomain, Reg, F> + StepLengthBound<F, Dat>, Plot: Plotter<P::ReturnMapping, Dat::DerivativeDomain, RNDM<N, F>>, { - run_fb_dom(f, reg, prox_penalty, init, None, cont) + run_fb_dom(f, reg, prox_penalty, init, &mut FullSpace, cont) } /// Runs FB-style algorithms if `alg` so requests and `prox_penalty` matches. @@ -1028,7 +1026,7 @@ Option<RNDM<N, F>>, String, ), - domain: Option<&P::Dom>, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, cont: impl FnOnce( (&AlgorithmConfig<F>, I, Plot, Option<RNDM<N, F>>, String), ) -> DynResult<RNDM<N, F>>, @@ -1049,7 +1047,16 @@ match alg { &AlgorithmConfig::FB(ref algconfig, prox_type) if prox_type == pt => { print!("{running}"); - pointsource_fb_reg(f, reg, prox_penalty, algconfig, iterator, plotter, μ0) + pointsource_fb_dom( + f, + reg, + prox_penalty, + algconfig, + iterator, + plotter, + μ0, + domain, + ) } &AlgorithmConfig::FISTA(ref algconfig, prox_type) if prox_type == pt => { print!("{running}"); @@ -1057,7 +1064,16 @@ } &AlgorithmConfig::SlidingFB(ref algconfig, prox_type) if prox_type == pt => { print!("{running}"); - pointsource_sliding_fb_dom(f, reg, prox_penalty, algconfig, iterator, plotter, μ0, domain) + pointsource_sliding_fb( + f, + reg, + prox_penalty, + algconfig, + iterator, + plotter, + μ0, + domain, + ) } _ => cont((alg, iterator, plotter, μ0, running)), } @@ -1294,11 +1310,29 @@ save_extra, (μ0, z, y), |p| { - run_pdps_pair_dom(&f, ®, &RadonSquared, &opKz, &fnR, &fnH, p, None, |q| { - run_pdps_pair_dom(&f, ®, &op𝒟, &opKz, &fnR, &fnH, q, None, |_| { - Err(NotImplemented.into()) - }) - }) + run_pdps_pair_dom( + &f, + ®, + &RadonSquared, + &opKz, + &fnR, + &fnH, + p, + &mut FullSpace, + |q| { + run_pdps_pair_dom( + &f, + ®, + &op𝒟, + &opKz, + &fnR, + &fnH, + q, + &mut FullSpace, + |_| Err(NotImplemented.into()), + ) + }, + ) .map(|Pair(μ, z)| (μ, z)) }, ) @@ -1314,11 +1348,29 @@ save_extra, (μ0, z, y), |p| { - run_pdps_pair_dom(&f, ®, &RadonSquared, &opKz, &fnR, &fnH, p, None, |q| { - run_pdps_pair_dom(&f, ®, &op𝒟, &opKz, &fnR, &fnH, q, None, |_| { - Err(NotImplemented.into()) - }) - }) + run_pdps_pair_dom( + &f, + ®, + &RadonSquared, + &opKz, + &fnR, + &fnH, + p, + &mut FullSpace, + |q| { + run_pdps_pair_dom( + &f, + ®, + &op𝒟, + &opKz, + &fnR, + &fnH, + q, + &mut FullSpace, + |_| Err(NotImplemented.into()), + ) + }, + ) .map(|Pair(μ, z)| (μ, z)) }, ) @@ -1379,7 +1431,17 @@ for<'b> H::Conjugate<'b>: Prox<Y>, Plot: Plotter<P::ReturnMapping, S, RNDM<N, F>>, { - run_pdps_pair_dom(f, reg, prox_penalty, opKz, fnR, fnH, init, None, cont) + run_pdps_pair_dom( + f, + reg, + prox_penalty, + opKz, + fnR, + fnH, + init, + &mut FullSpace, + cont, + ) } pub fn run_pdps_pair_dom<F, S, Dat, Reg, Z, R, Y, KOpZ, H, P, I, Plot, const N: usize>( @@ -1396,7 +1458,7 @@ (Option<RNDM<N, F>>, Z, Y), String, ), - domain: Option<&P::Dom>, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, cont: impl FnOnce( ( &AlgorithmConfig<F>, @@ -1436,7 +1498,7 @@ match alg { &AlgorithmConfig::ForwardPDPS(ref algconfig, prox_type) if prox_type == pt => { print!("{running}"); - pointsource_forward_pdps_pair( + pointsource_forward_pdps_pair_dom( f, reg, prox_penalty, @@ -1447,6 +1509,7 @@ opKz, fnR, fnH, + domain, ) } &AlgorithmConfig::SlidingPDPS(ref algconfig, prox_type) if prox_type == pt => { @@ -1508,7 +1571,7 @@ // We should not need to explicitly require this: for<'b> &'b Loc<0, F>: Instance<Loc<0, F>>, { - run_fb_pair_dom(f, reg, prox_penalty, fnR, init, None, cont) + run_fb_pair_dom(f, reg, prox_penalty, fnR, init, &mut FullSpace, cont) } pub fn run_fb_pair_dom<F, S, Dat, Reg, Z, R, P, I, Plot, const N: usize>( @@ -1523,7 +1586,7 @@ (Option<RNDM<N, F>>, Z), String, ), - domain: Option<&P::Dom>, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, cont: impl FnOnce( ( &AlgorithmConfig<F>, @@ -1556,7 +1619,17 @@ match alg { &AlgorithmConfig::FB(ref algconfig, prox_type) if prox_type == pt => { print!("{running}"); - pointsource_fb_pair(f, reg, prox_penalty, algconfig, iterator, plotter, μ0z, fnR) + pointsource_fb_pair_dom( + f, + reg, + prox_penalty, + algconfig, + iterator, + plotter, + μ0z, + fnR, + domain, + ) } &AlgorithmConfig::SlidingFB(ref algconfig, prox_type) if prox_type == pt => { print!("{running}"); @@ -1569,7 +1642,7 @@ plotter, μ0z, fnR, - domain + domain, ) } _ => cont((alg, iterator, plotter, μ0z, running)),
--- a/src/sliding_fb.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/sliding_fb.rs Tue Aug 04 14:12:18 2026 -0500 @@ -11,6 +11,7 @@ use std::iter::Iterator; use std::ops::MulAssign; +use crate::domain::{AdmissibleDomain, FullSpace}; use crate::fb::*; use crate::forward_model::{BoundedCurvature, BoundedCurvatureGuess}; use crate::measures::merging::SpikeMerging; @@ -28,7 +29,6 @@ use alg_tools::mapping::{DifferentiableMapping, DifferentiableRealMapping, Mapping, RealMapping}; use alg_tools::nalgebra_support::ToNalgebraRealField; use alg_tools::norms::Norm; -use alg_tools::sets::{Set, Cube}; use anyhow::ensure; use std::ops::ControlFlow; @@ -223,36 +223,35 @@ const MINIMAL_PARTIAL_TRANSPORT: bool = true; const NEW_APPROACH: bool = true; -pub trait TransportProxPenalty<Domain, PreadjointCodomain, Reg, F = f64>: - ProxPenalty<Domain, PreadjointCodomain, Reg, F> +pub trait TransportProxPenalty<Point, PreadjointCodomain, Reg, F = f64>: + ProxPenalty<Point, PreadjointCodomain, Reg, F> where F: Float + ToNalgebraRealField, - Reg: SlidingRegTerm<Domain, F>, - Domain: Space + Clone, + Reg: SlidingRegTerm<Point, F>, + Point: Space + Clone, { type TransportStepLength; - type Dom : Set<Domain>; /// Constrution of initial transport `γ1` from initial measure `μ` and `v=F'(μ)` /// with step lengh τ and transport step length `θ_or_adaptive`. - fn initial_transport( + fn initial_transport<Dom: AdmissibleDomain<Point>>( &self, - γ: &mut Transport<Domain, F>, - μ: &DiscreteMeasure<Domain, F>, + γ: &mut Transport<Point, F>, + μ: &DiscreteMeasure<Point, F>, ε: F, τ: F, τθ_or_adaptive: &mut Self::TransportStepLength, v: &PreadjointCodomain, tconfig: &TransportConfig<F>, - domain: Option<&Self::Dom>, + domain: &Dom, ); /// A posteriori transport adaptation. fn aposteriori_transport( &self, - γ: &mut Transport<Domain, F>, - μ: &DiscreteMeasure<Domain, F>, - μ̆: &DiscreteMeasure<Domain, F>, + γ: &mut Transport<Point, F>, + μ: &DiscreteMeasure<Point, F>, + μ̆: &DiscreteMeasure<Point, F>, τv̆: &mut PreadjointCodomain, v: &mut PreadjointCodomain, extra: Option<F>, @@ -284,7 +283,6 @@ RNDM<N, F>: SpikeMerging<F>, { type TransportStepLength = TransportStepLength<F>; - type Dom = Cube<N, F>; fn get_transport_steplength( &self, @@ -301,7 +299,7 @@ ) } - fn initial_transport( + fn initial_transport<Dom: AdmissibleDomain<Loc<N, F>>>( &self, γ: &mut Transport<Loc<N, F>, F>, μ: &RNDM<N, F>, @@ -310,7 +308,7 @@ τθ_or_adaptive: &mut TransportStepLength<F>, v: &M, tconfig: &TransportConfig<F>, - domain: Option<&Cube<N, F>>, + domain: &Dom, ) { γ.do_init_transport(v, μ, τθ_or_adaptive, tconfig, domain); } @@ -392,7 +390,6 @@ RNDM<N, F>: SpikeMerging<F>, { type TransportStepLength = TransportStepLength<F>; - type Dom = Cube<N, F>; fn get_transport_steplength( &self, @@ -409,7 +406,7 @@ ) } - fn initial_transport( + fn initial_transport<Dom: AdmissibleDomain<Loc<N, F>>>( &self, γ: &mut Transport<Loc<N, F>, F>, μ: &RNDM<N, F>, @@ -418,7 +415,7 @@ τθ_or_adaptive: &mut TransportStepLength<F>, v: &M, tconfig: &TransportConfig<F>, - domain: Option<&Cube<N, F>>, + domain: &Dom, ) { γ.do_init_transport(v, μ, τθ_or_adaptive, tconfig, domain); } @@ -745,13 +742,13 @@ } /// Helper for initial transport. Called from [`TransportProxPenalty::initial_transport`]. - pub(super) fn do_init_transport<D>( + pub(super) fn do_init_transport<D, Dom: AdmissibleDomain<Loc<N, F>>>( &mut self, v: &D, μ: &RNDM<N, F>, τθ_or_adaptive: &mut TransportStepLength<F>, tconfig: &TransportConfig<F>, - domain: Option<&Cube<N, F>>, + domain: &Dom, ) where D: DifferentiableRealMapping<N, F>, { @@ -819,7 +816,7 @@ let dv_x = v.differential(&ρ.x); let g = &dv_x * (ρ.α_γ.signum() * τθ); let tmp = ρ.x - g; - ρ.y = if domain.is_none_or(|d| d.contains(&tmp)) { tmp } else { ρ.x }; + ρ.y = if domain.contains(&tmp) { tmp } else { ρ.x }; let n = g.norm2(); if n >= F::EPSILON { // Estimate Lipschitz factor of ∇v @@ -845,7 +842,7 @@ for ρ in self.iter_mut() { if ρ.fail_count <= tconfig.max_fail { let tmp = ρ.x - v.differential(&ρ.x) * (ρ.α_γ.signum() * τθ); - ρ.y = if domain.is_none_or(|d| d.contains(&tmp)) { tmp } else { ρ.x }; + ρ.y = if domain.contains(&tmp) { tmp } else { ρ.x }; } } } @@ -978,7 +975,7 @@ /// may be given. /// Inertia is currently not supported. #[replace_float_literals(F::cast_from(literal))] -pub fn pointsource_sliding_fb_dom<F, I, Dat, Reg, Plot, P, const N: usize>( +pub fn pointsource_sliding_fb<F, I, Dat, Reg, Plot, P, const N: usize>( f: &Dat, reg: &Reg, prox_penalty: &P, @@ -986,7 +983,7 @@ iterator: I, mut plotter: Plot, μ0: Option<RNDM<N, F>>, - domain: Option<&P::Dom>, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, ) -> DynResult<RNDM<N, F>> where F: Float + ToNalgebraRealField, @@ -1049,7 +1046,7 @@ &mut τθ_or_adaptive, &v, &config.transport, - domain + domain, ); let mut attempts = 0; @@ -1107,8 +1104,8 @@ // Merge spikes. // This crucially expects the merge routine to be stable with respect to spike locations, // and not to performing any pruning. That is be to done below simultaneously for γ. - config.insertion.if_merge_now(&state, |ins| { - let m = prox_penalty.merge_spikes( + let did_merge = config.insertion.if_merge_now(&state, |ins| { + stats.merged += prox_penalty.merge_spikes( &mut μ, &mut τv̆, &μ̆, @@ -1116,32 +1113,35 @@ ε, ins, ®, - Some(|μ̃: &RNDM<N, F>| f.apply(μ̃) + reg.apply(μ̃) ), + Some(|μ̃: &RNDM<N, F>| f.apply(μ̃) + reg.apply(μ̃)), ); - //if m > 0 { - stats.merged += m; - //v = f.differential(&μ); - //} - }); + γ.prune_compat(&mut μ, &mut stats); - γ.prune_compat(&mut μ, &mut stats); + if config.insertion.random_insertions { + if let Some(x) = domain.random_point() { + μ += DeltaMeasure { x, α: 0.0 }; + stats.inserted += 1; + } + } - // Do extra weight optimisation step heuristic - for _ in 1..config.insertion.extra_weight_optimisation_steps { - τv̆ = f.differential(&μ) * τ; - prox_penalty.reweigh( - &mut μ, - &mut τv̆, - τ, - ε, - &config.insertion, - ®, - &state, - &mut stats, - )?; - } + // Do extra weight optimisation step heuristic + for _ in 1..config.insertion.extra_weight_optimisation_steps { + τv̆ = f.differential(&μ) * τ; + prox_penalty.reweigh( + &mut μ, + &mut τv̆, + τ, + ε, + &config.insertion, + ®, + &state, + &mut stats, + )?; + } + Ok(()) + })?; - if config.insertion.extra_weight_optimisation_steps > 0 { + if did_merge.is_none() { γ.prune_compat(&mut μ, &mut stats); } @@ -1185,5 +1185,14 @@ P: TransportProxPenalty<Loc<N, F>, Dat::DerivativeDomain, Reg, F> + StepLengthBound<F, Dat>, Plot: Plotter<P::ReturnMapping, Dat::DerivativeDomain, RNDM<N, F>>, { - pointsource_sliding_fb_dom(f, reg, prox_penalty, config, iterator, plotter, μ0, None) + pointsource_sliding_fb( + f, + reg, + prox_penalty, + config, + iterator, + plotter, + μ0, + &mut FullSpace, + ) }
--- a/src/sliding_pdps.rs Tue Aug 04 13:46:29 2026 -0500 +++ b/src/sliding_pdps.rs Tue Aug 04 14:12:18 2026 -0500 @@ -3,6 +3,7 @@ primal-dual proximal splitting method. */ +use crate::domain::{AdmissibleDomain, FullSpace}; use crate::fb::*; use crate::forward_model::{BoundedCurvature, BoundedCurvatureGuess}; use crate::measures::merging::SpikeMerging; @@ -14,7 +15,6 @@ use crate::types::*; use alg_tools::bounds::MinMaxMapping; use alg_tools::convex::{Conjugable, Prox, Zero}; -use alg_tools::norms::L2; use alg_tools::direct_product::Pair; use alg_tools::error::DynResult; use alg_tools::euclidean::ClosedEuclidean; @@ -24,7 +24,9 @@ }; use alg_tools::mapping::{DifferentiableMapping, DifferentiableRealMapping, Instance}; use alg_tools::nalgebra_support::ToNalgebraRealField; +use alg_tools::norms::L2; use anyhow::ensure; +use measures::DeltaMeasure; use numeric_literals::replace_float_literals; use serde::{Deserialize, Serialize}; @@ -96,7 +98,7 @@ opKz: &KOpZ, fnR: &R, fnH: &H, - domain: Option<&P::Dom>, + domain: &mut impl AdmissibleDomain<Loc<N, F>>, ) -> DynResult<MeasureZ<F, Z, N>> where F: Float + ToNalgebraRealField, @@ -306,8 +308,8 @@ // Merge spikes. // This crucially expects the merge routine to be stable with respect to spike locations, // and not to performing any pruning. That is be to done below simultaneously for γ. - config.insertion.if_merge_now(&state, |ins| { - let m = prox_penalty.merge_spikes( + let did_merge = config.insertion.if_merge_now(&state, |ins| { + stats.merged += prox_penalty.merge_spikes( &mut μ, &mut τv̆, &μ̆, @@ -317,39 +319,43 @@ ®, Some(|μ̃: &RNDM<N, F>| f.apply(Pair(μ̃, &z)) + reg.apply(μ̃)), ); - if m > 0 { - stats.merged += m; + γ.prune_compat(&mut μ, &mut stats); + + if config.insertion.random_insertions { + if let Some(x) = domain.random_point() { + μ += DeltaMeasure { x, α: 0.0 }; + stats.inserted += 1; + } } - }); - - γ.prune_compat(&mut μ, &mut stats); - for _ in 0..config.insertion.extra_weight_optimisation_steps { - // Do extra weight optimisation step heuristic - let Pair(v, mut _z_tmp) = f.differential(Pair(&μ, &z)); - τv̆ = v * τ; - prox_penalty.reweigh( - &mut μ, - &mut τv̆, - τ, - ε, - &config.insertion, - ®, - &state, - &mut stats, - )?; + for _ in 0..config.insertion.extra_weight_optimisation_steps { + // Do extra weight optimisation step heuristic + let Pair(v, mut _z_tmp) = f.differential(Pair(&μ, &z)); + τv̆ = v * τ; + prox_penalty.reweigh( + &mut μ, + &mut τv̆, + τ, + ε, + &config.insertion, + ®, + &state, + &mut stats, + )?; - // opKz_adj.apply_add(&mut z_tmp, &y); - // z_tmp.axpy(1.0, &z, -σ_p); - // let z_new = fnR.prox(σ_p, z_tmp); - // z.axpy(1.0 + ω, &z_new, -ω); - // //z = (z - &z_new) * (-ω) + &z_new; - // opKz.gemv(&mut y, σ_d, z, 1.0); - // y = starH.prox(σ_d, y); - // z = z_new; - } + // opKz_adj.apply_add(&mut z_tmp, &y); + // z_tmp.axpy(1.0, &z, -σ_p); + // let z_new = fnR.prox(σ_p, z_tmp); + // z.axpy(1.0 + ω, &z_new, -ω); + // //z = (z - &z_new) * (-ω) + &z_new; + // opKz.gemv(&mut y, σ_d, z, 1.0); + // y = starH.prox(σ_d, y); + // z = z_new; + } + Ok(()) + })?; - if config.insertion.extra_weight_optimisation_steps > 0 { + if did_merge.is_none() { γ.prune_compat(&mut μ, &mut stats); } @@ -396,7 +402,7 @@ (μ0, z): (Option<RNDM<N, F>>, Z), //opKμ : KOpM, fnR: &R, - domain: Option<&P::Dom> + domain: &mut impl AdmissibleDomain<Loc<N, F>>, ) -> DynResult<MeasureZ<F, Z, N>> where F: Float + ToNalgebraRealField, @@ -437,11 +443,10 @@ &opKz, fnR, &fnH, - domain + domain, ) } - #[deprecated] /// Version of [`pointsource_sliding_pdps_pair`] without domain specification. #[replace_float_literals(F::cast_from(literal))] @@ -517,7 +522,7 @@ opKz, fnR, fnH, - None, + &mut FullSpace, ) } @@ -574,6 +579,6 @@ &opKz, fnR, &fnH, - None + &mut FullSpace, ) }