diff -r aec67cdd6b14 -r efa60bc4f743 src/regularisation.rs --- a/src/regularisation.rs Tue Aug 01 10:32:12 2023 +0300 +++ b/src/regularisation.rs Thu Aug 29 00:00:00 2024 -0500 @@ -34,9 +34,13 @@ use crate::subproblem::{ nonneg::quadratic_nonneg, unconstrained::quadratic_unconstrained, + l1squared_unconstrained::l1squared_unconstrained, + l1squared_nonneg::l1squared_nonneg }; use alg_tools::iterate::AlgIteratorFactory; +use std::cmp::Ordering::{Greater, Less, Equal}; + /// The regularisation term $α\\|μ\\|\_{ℳ(Ω)} + δ_{≥ 0}(μ)$ for [`pointsource_fb_reg`] and other /// algorithms. /// @@ -131,6 +135,23 @@ config : &FBGenericConfig ) -> usize; + /// Approximately solve the problem + ///
$$ + /// \min_{x ∈ ℝ^n} \frac{1}{2} |x-y|_1^2 - g^⊤ x + τ G(x) + /// $$
+ /// for $G$ depending on the trait implementation. + /// + /// Returns the number of iterations taken. + fn solve_findim_l1squared( + &self, + y : &DVector, + g : &DVector, + τ : F, + x : &mut DVector, + ε : F, + config : &FBGenericConfig + ) -> usize; + /// Find a point where `d` may violate the tolerance `ε`. /// /// If `skip_by_rough_check` is set, do not find the point if a rough check indicates that we @@ -151,8 +172,37 @@ where BT : BTSearch>, G : SupportGenerator, G::SupportType : Mapping,Codomain=F> + + LocalAnalysis, N> { + self.find_tolerance_violation_slack(d, τ, ε, skip_by_rough_check, config, F::ZERO) + } + + /// Find a point where `d` may violate the tolerance `ε`. + /// + /// This version includes a `slack` parameter to expand the tolerances. + /// It is used for Radon-norm squared proximal term in [`crate::radon_fb`]. + /// + /// If `skip_by_rough_check` is set, do not find the point if a rough check indicates that we + /// are in bounds. `ε` is the current main tolerance and `τ` a scaling factor for the + /// regulariser. + /// + /// Returns `None` if `d` is in bounds either based on the rough check, or a more precise check + /// terminating early. Otherwise returns a possibly violating point, the value of `d` there, + /// and a boolean indicating whether the found point is in bounds. + fn find_tolerance_violation_slack( + &self, + d : &mut BTFN, + τ : F, + ε : F, + skip_by_rough_check : bool, + config : &FBGenericConfig, + slack : F, + ) -> Option<(Loc, F, bool)> + where BT : BTSearch>, + G : SupportGenerator, + G::SupportType : Mapping,Codomain=F> + LocalAnalysis, N>; + /// Verify that `d` is in bounds `ε` for a merge candidate `μ` /// /// `ε` is the current main tolerance and `τ` a scaling factor for the regulariser. @@ -169,6 +219,28 @@ G::SupportType : Mapping,Codomain=F> + LocalAnalysis, N>; + /// Verify that `d` is in bounds `ε` for a merge candidate `μ` + /// + /// This version is s used for Radon-norm squared proximal term in [`crate::radon_fb`]. + /// The [`DiscreteMeasure`]s `μ` and `radon_μ` are supposed to have same coordinates at + /// same agreeing indices. + /// + /// `ε` is the current main tolerance and `τ` a scaling factor for the regulariser. + fn verify_merge_candidate_radonsq( + &self, + d : &mut BTFN, + μ : &DiscreteMeasure, F>, + τ : F, + ε : F, + config : &FBGenericConfig, + radon_μ :&DiscreteMeasure, F>, + ) -> bool + where BT : BTSearch>, + G : SupportGenerator, + G::SupportType : Mapping,Codomain=F> + + LocalAnalysis, N>; + + /// TODO: document this fn target_bounds(&self, τ : F, ε : F) -> Option>; @@ -197,6 +269,9 @@ G : SupportGenerator, G::SupportType : Mapping,Codomain=F> + LocalAnalysis, N>; + + /// Convert bound on the regulariser to a bond on the Radon norm + fn radon_norm_bound(&self, b : F) -> F; } #[replace_float_literals(F::cast_from(literal))] @@ -215,30 +290,47 @@ ) -> usize { let inner_tolerance = ε * config.inner.tolerance_mult; let inner_it = config.inner.iterator_options.stop_target(inner_tolerance); - let inner_τ = config.inner.τ0 / mA_normest; - quadratic_nonneg(config.inner.method, mA, g, τ * self.α(), x, - inner_τ, inner_it) + quadratic_nonneg(mA, g, τ * self.α(), x, + mA_normest, &config.inner, inner_it) + } + fn solve_findim_l1squared( + &self, + y : &DVector, + g : &DVector, + τ : F, + x : &mut DVector, + ε : F, + config : &FBGenericConfig + ) -> usize { + let inner_tolerance = ε * config.inner.tolerance_mult; + let inner_it = config.inner.iterator_options.stop_target(inner_tolerance); + l1squared_nonneg(y, g, τ * self.α(), 1.0, x, + &config.inner, inner_it) + } + + #[inline] - fn find_tolerance_violation( + fn find_tolerance_violation_slack( &self, d : &mut BTFN, τ : F, ε : F, skip_by_rough_check : bool, config : &FBGenericConfig, + slack : F ) -> Option<(Loc, F, bool)> where BT : BTSearch>, G : SupportGenerator, G::SupportType : Mapping,Codomain=F> + LocalAnalysis, N> { let τα = τ * self.α(); - let keep_below = τα + ε; - let maximise_above = τα + ε * config.insertion_cutoff_factor; + let keep_below = τα + slack + ε; + let maximise_above = τα + slack + ε * config.insertion_cutoff_factor; let refinement_tolerance = ε * config.refinement.tolerance_mult; - // If preliminary check indicates that we are in bonds, and if it otherwise matches + // If preliminary check indicates that we are in bounds, and if it otherwise matches // the insertion strategy, skip insertion. if skip_by_rough_check && d.bounds().upper() <= keep_below { None @@ -271,9 +363,9 @@ return ( bnd.lower() >= keep_supp_above || - μ.iter_spikes().map(|&DeltaMeasure{ α : β, ref x }| { - (β == 0.0) || d.apply(x) >= keep_supp_above - }).all(std::convert::identity) + μ.iter_spikes().all(|&DeltaMeasure{ α, ref x }| { + (α == 0.0) || d.apply(x) >= keep_supp_above + }) ) && ( bnd.upper() <= keep_below || @@ -281,6 +373,50 @@ ) } + fn verify_merge_candidate_radonsq( + &self, + d : &mut BTFN, + μ : &DiscreteMeasure, F>, + τ : F, + ε : F, + config : &FBGenericConfig, + radon_μ :&DiscreteMeasure, F>, + ) -> bool + where BT : BTSearch>, + G : SupportGenerator, + G::SupportType : Mapping,Codomain=F> + + LocalAnalysis, N> { + let τα = τ * self.α(); + let refinement_tolerance = ε * config.refinement.tolerance_mult; + let merge_tolerance = config.merge_tolerance_mult * ε; + let slack = radon_μ.norm(Radon); + let bnd = d.bounds(); + + return { + μ.both_matching(radon_μ) + .all(|(α, rα, x)| { + let v = d.apply(x); + let (l1, u1) = match α.partial_cmp(&0.0).unwrap_or(Equal) { + Greater => (τα, τα), + _ => (F::NEG_INFINITY, τα), + // Less should not happen; treated as Equal + }; + let (l2, u2) = match rα.partial_cmp(&0.0).unwrap_or(Equal) { + Greater => (slack, slack), + Equal => (-slack, slack), + Less => (-slack, -slack), + }; + // TODO: both fail. + (l1 + l2 - merge_tolerance <= v) && (v <= u1 + u2 + merge_tolerance) + }) + } && { + let keep_below = τα + slack + merge_tolerance; + bnd.upper() <= keep_below + || + d.has_upper_bound(keep_below, refinement_tolerance, config.refinement.max_steps) + } + } + fn target_bounds(&self, τ : F, ε : F) -> Option> { let τα = τ * self.α(); Some(Bounds(τα - ε, τα + ε)) @@ -310,9 +446,12 @@ G : SupportGenerator, G::SupportType : Mapping,Codomain=F> + LocalAnalysis, N> { - //let w = |x| 1.0.min((ε + d.apply(x))/(τ * self.α())); - let τw = |x| τ.min((ε + d.apply(x))/self.α()); - τw(z) - τw(y) + let w = |x| 1.0.min((ε + d.apply(x))/(τ * self.α())); + w(z) - w(y) + } + + fn radon_norm_bound(&self, b : F) -> F { + b / self.α() } } @@ -331,28 +470,43 @@ ) -> usize { let inner_tolerance = ε * config.inner.tolerance_mult; let inner_it = config.inner.iterator_options.stop_target(inner_tolerance); - let inner_τ = config.inner.τ0 / mA_normest; - quadratic_unconstrained(config.inner.method, mA, g, τ * self.α(), x, - inner_τ, inner_it) + quadratic_unconstrained(mA, g, τ * self.α(), x, + mA_normest, &config.inner, inner_it) } - fn find_tolerance_violation( + fn solve_findim_l1squared( + &self, + y : &DVector, + g : &DVector, + τ : F, + x : &mut DVector, + ε : F, + config : &FBGenericConfig + ) -> usize { + let inner_tolerance = ε * config.inner.tolerance_mult; + let inner_it = config.inner.iterator_options.stop_target(inner_tolerance); + l1squared_unconstrained(y, g, τ * self.α(), 1.0, x, + &config.inner, inner_it) + } + + fn find_tolerance_violation_slack( &self, d : &mut BTFN, τ : F, ε : F, skip_by_rough_check : bool, config : &FBGenericConfig, + slack : F, ) -> Option<(Loc, F, bool)> where BT : BTSearch>, G : SupportGenerator, G::SupportType : Mapping,Codomain=F> + LocalAnalysis, N> { let τα = τ * self.α(); - let keep_below = τα + ε; - let keep_above = -τα - ε; - let maximise_above = τα + ε * config.insertion_cutoff_factor; - let minimise_below = -τα - ε * config.insertion_cutoff_factor; + let keep_below = τα + slack + ε; + let keep_above = -(τα + slack) - ε; + let maximise_above = τα + slack + ε * config.insertion_cutoff_factor; + let minimise_below = -(τα + slack) - ε * config.insertion_cutoff_factor; let refinement_tolerance = ε * config.refinement.tolerance_mult; // If preliminary check indicates that we are in bonds, and if it otherwise matches @@ -405,14 +559,13 @@ return ( (bnd.lower() >= keep_supp_pos_above && bnd.upper() <= keep_supp_neg_below) || - μ.iter_spikes().map(|&DeltaMeasure{ α : β, ref x }| { - use std::cmp::Ordering::*; + μ.iter_spikes().all(|&DeltaMeasure{ α : β, ref x }| { match β.partial_cmp(&0.0) { Some(Greater) => d.apply(x) >= keep_supp_pos_above, Some(Less) => d.apply(x) <= keep_supp_neg_below, _ => true, } - }).all(std::convert::identity) + }) ) && ( bnd.upper() <= keep_below || @@ -426,6 +579,56 @@ ) } + fn verify_merge_candidate_radonsq( + &self, + d : &mut BTFN, + μ : &DiscreteMeasure, F>, + τ : F, + ε : F, + config : &FBGenericConfig, + radon_μ : &DiscreteMeasure, F>, + ) -> bool + where BT : BTSearch>, + G : SupportGenerator, + G::SupportType : Mapping,Codomain=F> + + LocalAnalysis, N> { + let τα = τ * self.α(); + let refinement_tolerance = ε * config.refinement.tolerance_mult; + let merge_tolerance = config.merge_tolerance_mult * ε; + let slack = radon_μ.norm(Radon); + let bnd = d.bounds(); + + return { + μ.both_matching(radon_μ) + .all(|(α, rα, x)| { + let v = d.apply(x); + let (l1, u1) = match α.partial_cmp(&0.0).unwrap_or(Equal) { + Greater => (τα, τα), + Equal => (-τα, τα), + Less => (-τα, -τα), + }; + let (l2, u2) = match rα.partial_cmp(&0.0).unwrap_or(Equal) { + Greater => (slack, slack), + Equal => (-slack, slack), + Less => (-slack, -slack), + }; + (l1 + l2 - merge_tolerance <= v) && (v <= u1 + u2 + merge_tolerance) + }) + } && { + let keep_below = τα + slack + merge_tolerance; + bnd.upper() <= keep_below + || + d.has_upper_bound(keep_below, refinement_tolerance, + config.refinement.max_steps) + } && { + let keep_above = -τα - slack - merge_tolerance; + bnd.lower() >= keep_above + || + d.has_lower_bound(keep_above, refinement_tolerance, + config.refinement.max_steps) + } + } + fn target_bounds(&self, τ : F, ε : F) -> Option> { let τα = τ * self.α(); Some(Bounds(-τα - ε, τα + ε)) @@ -457,14 +660,14 @@ + LocalAnalysis, N> { let α = self.α(); - // let w = |x| { - // let dx = d.apply(x); - // ((-ε + dx)/(τ * α)).max(1.0.min(ε + dx)/(τ * α)) - // }; - let τw = |x| { + let w = |x| { let dx = d.apply(x); - ((-ε + dx)/α).max(τ.min(ε + dx)/α) + ((-ε + dx)/(τ * α)).max(1.0.min(ε + dx)/(τ * α)) }; - τw(z) - τw(y) + w(z) - w(y) + } + + fn radon_norm_bound(&self, b : F) -> F { + b / self.α() } } \ No newline at end of file