diff -r fb911f72e698 -r c5d8bd1a7728 src/prox_penalty/radon_squared.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/prox_penalty/radon_squared.rs Thu Jan 23 23:35:28 2025 +0100 @@ -0,0 +1,170 @@ +/*! +Solver for the point source localisation problem using a simplified forward-backward splitting method. + +Instead of the $𝒟$-norm of `fb.rs`, this uses a standard Radon norm for the proximal map. +*/ + +use numeric_literals::replace_float_literals; +use serde::{Serialize, Deserialize}; +use nalgebra::DVector; + +use alg_tools::iterate::{ + AlgIteratorIteration, + AlgIterator +}; +use alg_tools::norms::L2; +use alg_tools::linops::Mapping; +use alg_tools::bisection_tree::{ + BTFN, + Bounds, + BTSearch, + SupportGenerator, + LocalAnalysis, +}; +use alg_tools::mapping::RealMapping; +use alg_tools::nalgebra_support::ToNalgebraRealField; + +use crate::types::*; +use crate::measures::{ + RNDM, + DeltaMeasure, + Radon, +}; +use crate::measures::merging::SpikeMerging; +use crate::regularisation::RegTerm; +use crate::forward_model::{ + ForwardModel, + AdjointProductBoundedBy +}; +use super::{ + FBGenericConfig, + ProxPenalty, +}; + +/// Radon-norm squared proximal penalty + +#[derive(Copy,Clone,Serialize,Deserialize)] +pub struct RadonSquared; + +#[replace_float_literals(F::cast_from(literal))] +impl +ProxPenalty, Reg, N> for RadonSquared +where + F : Float + ToNalgebraRealField, + GA : SupportGenerator + Clone, + BTA : BTSearch>, + S: RealMapping + LocalAnalysis, N>, + Reg : RegTerm, + RNDM : SpikeMerging, +{ + type ReturnMapping = BTFN; + + fn insert_and_reweigh( + &self, + μ : &mut RNDM, + τv : &mut BTFN, + μ_base : &RNDM, + ν_delta: Option<&RNDM>, + τ : F, + ε : F, + config : &FBGenericConfig, + reg : &Reg, + _state : &AlgIteratorIteration, + stats : &mut IterInfo, + ) -> (Option, bool) + where + I : AlgIterator + { + assert!(ν_delta.is_none(), "Transport not implemented for Radon-squared prox term"); + + let mut y = μ_base.masses_vec(); + + 'i_and_w: for i in 0..=1 { + // Optimise weights + if μ.len() > 0 { + // Form finite-dimensional subproblem. The subproblem references to the original μ^k + // from the beginning of the iteration are all contained in the immutable c and g. + // TODO: observe negation of -τv after switch from minus_τv: finite-dimensional + // problems have not yet been updated to sign change. + let g̃ = DVector::from_iterator(μ.len(), + μ.iter_locations() + .map(|ζ| - F::to_nalgebra_mixed(τv.apply(ζ)))); + let mut x = μ.masses_dvector(); + // Ugly hack because DVector::push doesn't push but copies. + let yvec = DVector::from_column_slice(y.as_slice()); + // Solve finite-dimensional subproblem. + stats.inner_iters += reg.solve_findim_l1squared(&yvec, &g̃, τ, &mut x, ε, config); + + // Update masses of μ based on solution of finite-dimensional subproblem. + μ.set_masses_dvector(&x); + } + + if i>0 { + // Simple debugging test to see if more inserts would be needed. Doesn't seem so. + //let n = μ.dist_matching(μ_base); + //println!("{:?}", reg.find_tolerance_violation_slack(τv, τ, ε, false, config, n)); + break 'i_and_w + } + + // Calculate ‖μ - μ_base‖_ℳ + let n = μ.dist_matching(μ_base); + + // Find a spike to insert, if needed. + // This only check the overall tolerances, not tolerances on support of μ-μ_base or μ, + // which are supposed to have been guaranteed by the finite-dimensional weight optimisation. + match reg.find_tolerance_violation_slack(τv, τ, ε, false, config, n) { + None => { break 'i_and_w }, + Some((ξ, _v_ξ, _in_bounds)) => { + // Weight is found out by running the finite-dimensional optimisation algorithm + // above + *μ += DeltaMeasure { x : ξ, α : 0.0 }; + //*μ_base += DeltaMeasure { x : ξ, α : 0.0 }; + y.push(0.0.to_nalgebra_mixed()); + stats.inserted += 1; + } + }; + } + + (None, true) + } + + fn merge_spikes( + &self, + μ : &mut RNDM, + τv : &mut BTFN, + μ_base : &RNDM, + τ : F, + ε : F, + config : &FBGenericConfig, + reg : &Reg, + ) -> usize + { + μ.merge_spikes(config.merging, |μ_candidate| { + // Important: μ_candidate's new points are afterwards, + // and do not conflict with μ_base. + // TODO: could simplify to requiring μ_base instead of μ_radon. + // but may complicate with sliding base's exgtra points that need to be + // after μ_candidate's extra points. + // TODO: doesn't seem to work, maybe need to merge μ_base as well? + // Although that doesn't seem to make sense. + let μ_radon = μ_candidate.sub_matching(μ_base); + reg.verify_merge_candidate_radonsq(τv, μ_candidate, τ, ε, &config, &μ_radon) + //let n = μ_candidate.dist_matching(μ_base); + //reg.find_tolerance_violation_slack(τv, τ, ε, false, config, n).is_none() + }) + } +} + + +impl AdjointProductBoundedBy, RadonSquared> +for A +where + F : Float, + A : ForwardModel, F> +{ + type FloatType = F; + + fn adjoint_product_bound(&self, _ : &RadonSquared) -> Option { + self.opnorm_bound(Radon, L2).powi(2).into() + } +}