diff -r 759634edb536 -r 8f1e57821d76 src/sliding_fb.rs --- a/src/sliding_fb.rs Tue Aug 04 20:11:10 2026 -0500 +++ b/src/sliding_fb.rs Thu Aug 06 11:14:15 2026 -0500 @@ -24,11 +24,11 @@ use alg_tools::bounds::{Bounds, MinMaxMapping}; use alg_tools::error::DynResult; use alg_tools::euclidean::Euclidean; -use alg_tools::instance::Space; +use alg_tools::instance::{Instance, Space}; use alg_tools::iterate::AlgIteratorFactory; use alg_tools::mapping::{DifferentiableMapping, DifferentiableRealMapping, Mapping, RealMapping}; use alg_tools::nalgebra_support::ToNalgebraRealField; -use alg_tools::norms::Norm; +use alg_tools::norms::{Dist, Norm, L2}; use anyhow::ensure; use std::ops::ControlFlow; @@ -193,11 +193,11 @@ } #[derive(Clone, Debug, Serialize)] -pub struct SingleTransport { +pub struct SingleTransport { /// Source point - x: Domain, + x: U, /// Target point - y: Domain, + y: U, /// Original mass α_μ_orig: F, /// Transported mass @@ -211,8 +211,8 @@ } #[derive(Clone, Debug, Serialize)] -pub struct Transport { - vec: Vec>, +pub struct Transport { + vec: Vec>, } /// Whether partially transported points are allowed. @@ -223,21 +223,21 @@ const MINIMAL_PARTIAL_TRANSPORT: bool = true; const NEW_APPROACH: bool = true; -pub trait TransportProxPenalty: - ProxPenalty +pub trait TransportProxPenalty: + ProxPenalty where F: Float + ToNalgebraRealField, - Reg: SlidingRegTerm, - Point: Space + Clone, + Reg: SlidingRegTerm, + U: Space + Clone, { type TransportStepLength; /// 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>( &self, - γ: &mut Transport, - μ: &DiscreteMeasure, + γ: &mut Transport, + μ: &DiscreteMeasure, ε: F, τ: F, τθ_or_adaptive: &mut Self::TransportStepLength, @@ -249,9 +249,9 @@ /// A posteriori transport adaptation. fn aposteriori_transport( &self, - γ: &mut Transport, - μ: &DiscreteMeasure, - μ̆: &DiscreteMeasure, + γ: &mut Transport, + μ: &DiscreteMeasure, + μ̆: &DiscreteMeasure, τv̆: &mut PreadjointCodomain, v: &mut PreadjointCodomain, extra: Option, @@ -377,14 +377,12 @@ } #[replace_float_literals(F::cast_from(literal))] -impl TransportProxPenalty, M, Reg, F> for 𝒟 +impl TransportProxPenalty, M, Reg, F> for 𝒟 where F: Float + ToNalgebraRealField, 𝒟: DiscreteMeasureOp, F>, 𝒟::Codomain: RealMapping, M: MinMaxMapping, F> + DifferentiableRealMapping, - for<'a> &'a M: std::ops::Add<𝒟::PreCodomain, Output = O>, - O: MinMaxMapping, F>, Reg: SlidingRegTerm, F>, Self: ProxPenalty, M, Reg, F>, RNDM: SpikeMerging, @@ -608,25 +606,37 @@ } } +impl Norm for Transport { + fn norm(&self, _: Radon) -> F { + self.iter().map(|ρ| ρ.α_γ.abs()).sum() + } +} + +impl MulAssign for Transport { + fn mul_assign(&mut self, factor: F) { + for ρ in self.iter_mut() { + ρ.α_γ *= factor; + } + } +} + #[replace_float_literals(F::cast_from(literal))] -impl Transport, F> { +impl Transport { pub(crate) fn new() -> Self { Transport { vec: Vec::new() } } - pub(crate) fn iter(&self) -> impl Iterator, F>> { + pub(crate) fn iter(&self) -> impl Iterator> { self.vec.iter() } - pub(crate) fn iter_mut( - &mut self, - ) -> impl Iterator, F>> { + pub(crate) fn iter_mut(&mut self) -> impl Iterator> { self.vec.iter_mut() } pub(crate) fn extend(&mut self, it: I) where - I: IntoIterator, F>>, + I: IntoIterator>, { self.vec.extend(it) } @@ -635,62 +645,44 @@ self.vec.len() } - // pub(crate) fn dist_matching(&self, μ: &RNDM) -> F { - // self.iter() - // .zip(μ.iter_spikes()) - // .map(|(ρ, δ)| (ρ.α_γ - δ.α).abs()) - // .sum() - // } - - /// Construct `μ̆`, replacing the contents of `μ`. - pub(crate) fn μ̆_into(&self, μ: &mut RNDM) { - assert!(self.len() <= μ.len()); - - // First transported points - for (δ, ρ) in izip!(μ.iter_spikes_mut(), self.iter()) { - if ρ.α_γ.abs() > 0.0 { - // Transport – transported point - δ.α = ρ.α_γ; - δ.x = ρ.y; - } else { - // No transport – original point - δ.α = ρ.α_μ_orig; - δ.x = ρ.x; - } - } - - // Then source points with partial transport - let mut i = self.len(); - // This can cause the number of points to explode, so cannot have partial transport. - for ρ in self.iter() { - let α = ρ.α_μ_orig - ρ.α_γ; - if ρ.α_γ.abs() > F::EPSILON && α.abs() > F::EPSILON { - let δ = DeltaMeasure { α, x: ρ.x }; - if i < μ.len() { - μ[i] = δ; - } else { - μ.push(δ) - } - i += 1; - } - } - μ.truncate(i); - } - /// Returns $‖μ\^k - π\_♯\^0γ\^{k+1}‖$ pub(crate) fn μ0_minus_γ0_radon(&self) -> F { self.vec.iter().map(|ρ| (ρ.α_μ_orig - ρ.α_γ).abs()).sum() } + /// Prune spikes with zero weight. To maintain correct ordering between μ and γ, also the + /// latter needs to be pruned when μ is. + pub(crate) fn prune_compat(&mut self, μ: &mut DiscreteMeasure, stats: &mut IterInfo) { + assert!(self.vec.len() <= μ.len()); + let old_len = μ.len(); + for (ρ, δ) in self.vec.iter_mut().zip(μ.iter_spikes()) { + ρ.retain = δ.α != F::ZERO; + } + // Do not prune new insertions on first round. + μ.prune_until(self.vec.len()); + stats.pruned += old_len - μ.len(); + self.vec.retain(|ρ| ρ.retain); + assert!(self.vec.len() <= μ.len()); + } +} + +#[replace_float_literals(F::cast_from(literal))] +impl Transport +where + U: Dist, + F: Float, + for<'a> &'a U: Instance, +{ /// Returns $∫ c_2 d|γ|$ pub(crate) fn c2integral(&self) -> F { + let sq2 = |x| (x * x) / 2.0; self.vec .iter() - .map(|ρ| ρ.y.dist2_squared(&ρ.x) / 2.0 * ρ.α_γ.abs()) + .map(|ρ| sq2(ρ.y.dist(&ρ.x, L2)) * ρ.α_γ.abs()) .sum() } - pub(crate) fn get_transport_stats(&self, stats: &mut IterInfo, μ: &RNDM) { + pub(crate) fn get_transport_stats(&self, stats: &mut IterInfo, μ: &DiscreteMeasure) { // TODO: This doesn't take into account μ[i].α becoming zero in the latest tranport // attempt, for i < self.len(), when a corresponding source term also exists with index // j ≥ self.len(). For now, we let that be reflected in the prune count. @@ -726,22 +718,55 @@ }) }; } +} - /// Prune spikes with zero weight. To maintain correct ordering between μ and γ, also the - /// latter needs to be pruned when μ is. - pub(crate) fn prune_compat(&mut self, μ: &mut RNDM, stats: &mut IterInfo) { - assert!(self.vec.len() <= μ.len()); - let old_len = μ.len(); - for (ρ, δ) in self.vec.iter_mut().zip(μ.iter_spikes()) { - ρ.retain = δ.α != F::ZERO; +#[replace_float_literals(F::cast_from(literal))] +impl Transport { + // pub(crate) fn dist_matching(&self, μ: &RNDM) -> F { + // self.iter() + // .zip(μ.iter_spikes()) + // .map(|(ρ, δ)| (ρ.α_γ - δ.α).abs()) + // .sum() + // } + + /// Construct `μ̆`, replacing the contents of `μ`. + pub(crate) fn μ̆_into(&self, μ: &mut DiscreteMeasure) { + assert!(self.len() <= μ.len()); + + // First transported points + for (δ, ρ) in izip!(μ.iter_spikes_mut(), self.iter()) { + if ρ.α_γ.abs() > 0.0 { + // Transport – transported point + δ.α = ρ.α_γ; + δ.x = ρ.y.clone(); + } else { + // No transport – original point + δ.α = ρ.α_μ_orig; + δ.x = ρ.x.clone(); + } } - // Do not prune new insertions on first round. - μ.prune_until(self.vec.len()); - stats.pruned += old_len - μ.len(); - self.vec.retain(|ρ| ρ.retain); - assert!(self.vec.len() <= μ.len()); + + // Then source points with partial transport + let mut i = self.len(); + // This can cause the number of points to explode, so cannot have partial transport. + for ρ in self.iter() { + let α = ρ.α_μ_orig - ρ.α_γ; + if ρ.α_γ.abs() > F::EPSILON && α.abs() > F::EPSILON { + let δ = DeltaMeasure { α, x: ρ.x.clone() }; + if i < μ.len() { + μ[i] = δ; + } else { + μ.push(δ) + } + i += 1; + } + } + μ.truncate(i); } +} +#[replace_float_literals(F::cast_from(literal))] +impl Transport, F> { /// Helper for initial transport. Called from [`TransportProxPenalty::initial_transport`]. pub(super) fn do_init_transport>>( &mut self, @@ -955,20 +980,6 @@ } } -impl Norm for Transport, F> { - fn norm(&self, _: Radon) -> F { - self.iter().map(|ρ| ρ.α_γ.abs()).sum() - } -} - -impl MulAssign for Transport, F> { - fn mul_assign(&mut self, factor: F) { - for ρ in self.iter_mut() { - ρ.α_γ *= factor; - } - } -} - /// Iteratively solve the pointsource localisation problem using sliding forward-backward /// splitting /// @@ -976,27 +987,29 @@ /// may be given. /// Inertia is currently not supported. #[replace_float_literals(F::cast_from(literal))] -pub fn pointsource_sliding_fb( +pub fn pointsource_sliding_fb( f: &Dat, reg: &Reg, prox_penalty: &P, config: &SlidingFBConfig, iterator: I, mut plotter: Plot, - μ0: Option>, - domain: &mut impl AdmissibleDomain>, -) -> DynResult> + μ0: Option>, + domain: &mut impl AdmissibleDomain, +) -> DynResult> where F: Float + ToNalgebraRealField, + U: Space + Clone + Dist, + for<'a> &'a U: Instance, I: AlgIteratorFactory>, - Dat: DifferentiableMapping, Codomain = F> + BoundedCurvature, + Dat: DifferentiableMapping, Codomain = F> + BoundedCurvature, Dat::DerivativeDomain: - DifferentiableRealMapping + ClosedMul + MinMaxMapping, F>, + DifferentiableMapping + ClosedMul + MinMaxMapping, //for<'a> Dat::Differential<'a>: Lipschitz<&'a P, FloatType = F>, - RNDM: SpikeMerging, - Reg: SlidingRegTerm, F>, - P: TransportProxPenalty, Dat::DerivativeDomain, Reg, F> + StepLengthBound, - Plot: Plotter>, + DiscreteMeasure: SpikeMerging, + Reg: SlidingRegTerm, + P: TransportProxPenalty + StepLengthBound, + Plot: Plotter>, { // Check parameters ensure!(config.τ0 > 0.0, "Invalid step length parameter"); @@ -1025,7 +1038,7 @@ let mut ε = tolerance.initial(); // Statistics - let full_stats = |μ: &RNDM, ε, stats| IterInfo { + let full_stats = |μ: &DiscreteMeasure, ε, stats| IterInfo { value: f.apply(μ) + reg.apply(μ), n_spikes: μ.len(), ε, @@ -1114,7 +1127,7 @@ ε, ins, ®, - Some(|μ̃: &RNDM| f.apply(μ̃) + reg.apply(μ̃)), + Some(|μ̃: &DiscreteMeasure| f.apply(μ̃) + reg.apply(μ̃)), ); // Prune. Do not prune new insertions on first round. γ.prune_compat(&mut μ, &mut stats); @@ -1167,26 +1180,28 @@ #[deprecated] /// Version of `pointsource_sliding_fb` without domain restriction -pub fn pointsource_sliding_fb_reg( +pub fn pointsource_sliding_fb_reg( f: &Dat, reg: &Reg, prox_penalty: &P, config: &SlidingFBConfig, iterator: I, plotter: Plot, - μ0: Option>, -) -> DynResult> + μ0: Option>, +) -> DynResult> where F: Float + ToNalgebraRealField, + U: Space + Clone + Dist, + for<'a> &'a U: Instance, I: AlgIteratorFactory>, - Dat: DifferentiableMapping, Codomain = F> + BoundedCurvature, + Dat: DifferentiableMapping, Codomain = F> + BoundedCurvature, Dat::DerivativeDomain: - DifferentiableRealMapping + ClosedMul + MinMaxMapping, F>, + DifferentiableMapping + ClosedMul + MinMaxMapping, //for<'a> Dat::Differential<'a>: Lipschitz<&'a P, FloatType = F>, - RNDM: SpikeMerging, - Reg: SlidingRegTerm, F>, - P: TransportProxPenalty, Dat::DerivativeDomain, Reg, F> + StepLengthBound, - Plot: Plotter>, + DiscreteMeasure: SpikeMerging, + Reg: SlidingRegTerm, + P: TransportProxPenalty + StepLengthBound, + Plot: Plotter>, { pointsource_sliding_fb( f,