diff -r 6105b5cd8d89 -r f0e8704d3f0e src/fb.rs
--- a/src/fb.rs Tue Aug 01 10:25:09 2023 +0300
+++ b/src/fb.rs Mon Feb 17 13:54:53 2025 -0500
@@ -6,10 +6,7 @@
* Valkonen T. - _Proximal methods for point source localisation_,
[arXiv:2212.02991](https://arxiv.org/abs/2212.02991).
-The main routine is [`pointsource_fb_reg`]. It is based on [`generic_pointsource_fb_reg`], which is
-also used by our [primal-dual proximal splitting][crate::pdps] implementation.
-
-FISTA-type inertia can also be enabled through [`FBConfig::meta`].
+The main routine is [`pointsource_fb_reg`].
## Problem
@@ -76,650 +73,94 @@
$$
-We solve this with either SSN or FB via [`quadratic_nonneg`] as determined by
-[`InnerSettings`] in [`FBGenericConfig::inner`].
+We solve this with either SSN or FB as determined by
+[`crate::subproblem::InnerSettings`] in [`FBGenericConfig::inner`].
*/
+use colored::Colorize;
use numeric_literals::replace_float_literals;
-use serde::{Serialize, Deserialize};
-use colored::Colorize;
-use nalgebra::{DVector, DMatrix};
+use serde::{Deserialize, Serialize};
-use alg_tools::iterate::{
- AlgIteratorFactory,
- AlgIteratorState,
-};
use alg_tools::euclidean::Euclidean;
-use alg_tools::linops::Apply;
-use alg_tools::sets::Cube;
-use alg_tools::loc::Loc;
-use alg_tools::mapping::Mapping;
-use alg_tools::bisection_tree::{
- BTFN,
- PreBTFN,
- Bounds,
- BTNodeLookup,
- BTNode,
- BTSearch,
- P2Minimise,
- SupportGenerator,
- LocalAnalysis,
- Bounded,
-};
+use alg_tools::instance::Instance;
+use alg_tools::iterate::AlgIteratorFactory;
+use alg_tools::linops::{Mapping, GEMV};
use alg_tools::mapping::RealMapping;
use alg_tools::nalgebra_support::ToNalgebraRealField;
+use crate::dataterm::{calculate_residual, DataTerm, L2Squared};
+use crate::forward_model::{AdjointProductBoundedBy, ForwardModel};
+use crate::measures::merging::SpikeMerging;
+use crate::measures::{DiscreteMeasure, RNDM};
+use crate::plot::{PlotLookup, Plotting, SeqPlotter};
+pub use crate::prox_penalty::{FBGenericConfig, ProxPenalty};
+use crate::regularisation::RegTerm;
use crate::types::*;
-use crate::measures::{
- DiscreteMeasure,
- DeltaMeasure,
-};
-use crate::measures::merging::{
- SpikeMergingMethod,
- SpikeMerging,
-};
-use crate::forward_model::ForwardModel;
-use crate::seminorms::{
- DiscreteMeasureOp, Lipschitz
-};
-use crate::subproblem::{
- nonneg::quadratic_nonneg,
- unconstrained::quadratic_unconstrained,
- InnerSettings,
- InnerMethod,
-};
-use crate::tolerance::Tolerance;
-use crate::plot::{
- SeqPlotter,
- Plotting,
- PlotLookup
-};
-use crate::regularisation::{
- NonnegRadonRegTerm,
- RadonRegTerm,
-};
-
-/// Method for constructing $μ$ on each iteration
-#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)]
-#[allow(dead_code)]
-pub enum InsertionStyle {
- /// Resuse previous $μ$ from previous iteration, optimising weights
- /// before inserting new spikes.
- Reuse,
- /// Start each iteration with $μ=0$.
- Zero,
-}
-
-/// Meta-algorithm type
-#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)]
-#[allow(dead_code)]
-pub enum FBMetaAlgorithm {
- /// No meta-algorithm
- None,
- /// FISTA-style inertia
- InertiaFISTA,
-}
/// Settings for [`pointsource_fb_reg`].
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[serde(default)]
-pub struct FBConfig {
+pub struct FBConfig {
/// Step length scaling
- pub τ0 : F,
- /// Meta-algorithm to apply
- pub meta : FBMetaAlgorithm,
+ pub τ0: F,
/// Generic parameters
- pub insertion : FBGenericConfig,
-}
-
-/// Settings for the solution of the stepwise optimality condition in algorithms based on
-/// [`generic_pointsource_fb_reg`].
-#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)]
-#[serde(default)]
-pub struct FBGenericConfig {
- /// Method for constructing $μ$ on each iteration; see [`InsertionStyle`].
- pub insertion_style : InsertionStyle,
- /// Tolerance for point insertion.
- pub tolerance : Tolerance,
- /// Stop looking for predual maximum (where to isert a new point) below
- /// `tolerance` multiplied by this factor.
- pub insertion_cutoff_factor : F,
- /// Settings for branch and bound refinement when looking for predual maxima
- pub refinement : RefinementSettings,
- /// Maximum insertions within each outer iteration
- pub max_insertions : usize,
- /// Pair `(n, m)` for maximum insertions `m` on first `n` iterations.
- pub bootstrap_insertions : Option<(usize, usize)>,
- /// Inner method settings
- pub inner : InnerSettings,
- /// Spike merging method
- pub merging : SpikeMergingMethod,
- /// Tolerance multiplier for merges
- pub merge_tolerance_mult : F,
- /// Spike merging method after the last step
- pub final_merging : SpikeMergingMethod,
- /// Iterations between merging heuristic tries
- pub merge_every : usize,
- /// Save $μ$ for postprocessing optimisation
- pub postprocessing : bool
+ pub generic: FBGenericConfig,
}
#[replace_float_literals(F::cast_from(literal))]
-impl Default for FBConfig {
+impl Default for FBConfig {
fn default() -> Self {
FBConfig {
- τ0 : 0.99,
- meta : FBMetaAlgorithm::None,
- insertion : Default::default()
- }
- }
-}
-
-#[replace_float_literals(F::cast_from(literal))]
-impl Default for FBGenericConfig {
- fn default() -> Self {
- FBGenericConfig {
- insertion_style : InsertionStyle::Reuse,
- tolerance : Default::default(),
- insertion_cutoff_factor : 1.0,
- refinement : Default::default(),
- max_insertions : 100,
- //bootstrap_insertions : None,
- bootstrap_insertions : Some((10, 1)),
- inner : InnerSettings {
- method : InnerMethod::SSN,
- .. Default::default()
- },
- merging : SpikeMergingMethod::None,
- //merging : Default::default(),
- final_merging : Default::default(),
- merge_every : 10,
- merge_tolerance_mult : 2.0,
- postprocessing : false,
+ τ0: 0.99,
+ generic: Default::default(),
}
}
}
-/// Trait for specialisation of [`generic_pointsource_fb_reg`] to basic FB, FISTA.
-///
-/// The idea is that the residual $Aμ - b$ in the forward step can be replaced by an arbitrary
-/// value. For example, to implement [primal-dual proximal splitting][crate::pdps] we replace it
-/// with the dual variable $y$. We can then also implement alternative data terms, as the
-/// (pre)differential of $F(μ)=F\_0(Aμ-b)$ is $F\'(μ) = A\_*F\_0\'(Aμ-b)$. In the case of the
-/// quadratic fidelity $F_0(y)=\frac{1}{2}\\|y\\|_2^2$ in a Hilbert space, of course,
-/// $F\_0\'(Aμ-b)=Aμ-b$ is the residual.
-pub trait FBSpecialisation, const N : usize> : Sized {
- /// Updates the residual and does any necessary pruning of `μ`.
- ///
- /// Returns the new residual and possibly a new step length.
- ///
- /// The measure `μ` may also be modified to apply, e.g., inertia to it.
- /// The updated residual should correspond to the residual at `μ`.
- /// See the [trait documentation][FBSpecialisation] for the use and meaning of the residual.
- ///
- /// The parameter `μ_base` is the base point of the iteration, typically the previous iterate,
- /// but for, e.g., FISTA has inertia applied to it.
- fn update(
- &mut self,
- μ : &mut DiscreteMeasure, F>,
- μ_base : &DiscreteMeasure, F>,
- ) -> (Observable, Option);
-
- /// Calculates the data term value corresponding to iterate `μ` and available residual.
- ///
- /// Inertia and other modifications, as deemed, necessary, should be applied to `μ`.
- ///
- /// The blanket implementation correspondsn to the 2-norm-squared data fidelity
- /// $\\|\text{residual}\\|\_2^2/2$.
- fn calculate_fit(
- &self,
- _μ : &DiscreteMeasure, F>,
- residual : &Observable
- ) -> F {
- residual.norm2_squared_div2()
- }
-
- /// Calculates the data term value at $μ$.
- ///
- /// Unlike [`Self::calculate_fit`], no inertia, etc., should be applied to `μ`.
- fn calculate_fit_simple(
- &self,
- μ : &DiscreteMeasure, F>,
- ) -> F;
-
- /// Returns the final iterate after any necessary postprocess pruning, merging, etc.
- fn postprocess(self, mut μ : DiscreteMeasure, F>, merging : SpikeMergingMethod)
- -> DiscreteMeasure, F>
- where DiscreteMeasure, F> : SpikeMerging {
- μ.merge_spikes_fitness(merging,
- |μ̃| self.calculate_fit_simple(μ̃),
- |&v| v);
- μ.prune();
- μ
- }
-
- /// Returns measure to be used for value calculations, which may differ from μ.
- fn value_μ<'c, 'b : 'c>(&'b self, μ : &'c DiscreteMeasure, F>)
- -> &'c DiscreteMeasure, F> {
- μ
- }
-}
-
-/// Specialisation of [`generic_pointsource_fb_reg`] to basic μFB.
-struct BasicFB<
- 'a,
- F : Float + ToNalgebraRealField,
- A : ForwardModel, F>,
- const N : usize
-> {
- /// The data
- b : &'a A::Observable,
- /// The forward operator
- opA : &'a A,
-}
-
-/// Implementation of [`FBSpecialisation`] for basic μFB forward-backward splitting.
-#[replace_float_literals(F::cast_from(literal))]
-impl<'a, F : Float + ToNalgebraRealField , A : ForwardModel, F>, const N : usize>
-FBSpecialisation for BasicFB<'a, F, A, N> {
- fn update(
- &mut self,
- μ : &mut DiscreteMeasure, F>,
- _μ_base : &DiscreteMeasure, F>
- ) -> (A::Observable, Option) {
- μ.prune();
- //*residual = self.opA.apply(μ) - self.b;
- let mut residual = self.b.clone();
- self.opA.gemv(&mut residual, 1.0, μ, -1.0);
- (residual, None)
- }
-
- fn calculate_fit_simple(
- &self,
- μ : &DiscreteMeasure, F>,
- ) -> F {
- let mut residual = self.b.clone();
- self.opA.gemv(&mut residual, 1.0, μ, -1.0);
- residual.norm2_squared_div2()
- }
-}
-
-/// Specialisation of [`generic_pointsource_fb_reg`] to FISTA.
-struct FISTA<
- 'a,
- F : Float + ToNalgebraRealField,
- A : ForwardModel, F>,
- const N : usize
-> {
- /// The data
- b : &'a A::Observable,
- /// The forward operator
- opA : &'a A,
- /// Current inertial parameter
- λ : F,
- /// Previous iterate without inertia applied.
- /// We need to store this here because `μ_base` passed to [`FBSpecialisation::update`] will
- /// have inertia applied to it, so is not useful to use.
- μ_prev : DiscreteMeasure, F>,
-}
-
-/// Implementation of [`FBSpecialisation`] for μFISTA inertial forward-backward splitting.
-#[replace_float_literals(F::cast_from(literal))]
-impl<'a, F : Float + ToNalgebraRealField, A : ForwardModel, F>, const N : usize>
-FBSpecialisation for FISTA<'a, F, A, N> {
- fn update(
- &mut self,
- μ : &mut DiscreteMeasure, F>,
- _μ_base : &DiscreteMeasure, F>
- ) -> (A::Observable, Option) {
- // Update inertial parameters
- let λ_prev = self.λ;
- self.λ = 2.0 * λ_prev / ( λ_prev + (4.0 + λ_prev * λ_prev).sqrt() );
- let θ = self.λ / λ_prev - self.λ;
- // Perform inertial update on μ.
- // This computes μ ← (1 + θ) * μ - θ * μ_prev, pruning spikes where both μ
- // and μ_prev have zero weight. Since both have weights from the finite-dimensional
- // subproblem with a proximal projection step, this is likely to happen when the
- // spike is not needed. A copy of the pruned μ without artithmetic performed is
- // stored in μ_prev.
- μ.pruning_sub(1.0 + θ, θ, &mut self.μ_prev);
-
- //*residual = self.opA.apply(μ) - self.b;
- let mut residual = self.b.clone();
- self.opA.gemv(&mut residual, 1.0, μ, -1.0);
- (residual, None)
- }
-
- fn calculate_fit_simple(
- &self,
- μ : &DiscreteMeasure, F>,
- ) -> F {
- let mut residual = self.b.clone();
- self.opA.gemv(&mut residual, 1.0, μ, -1.0);
- residual.norm2_squared_div2()
- }
-
- fn calculate_fit(
- &self,
- _μ : &DiscreteMeasure, F>,
- _residual : &A::Observable
- ) -> F {
- self.calculate_fit_simple(&self.μ_prev)
- }
-
- // For FISTA we need to do a final pruning as well, due to the limited
- // pruning that can be done on each step.
- fn postprocess(mut self, μ_base : DiscreteMeasure, F>, merging : SpikeMergingMethod)
- -> DiscreteMeasure, F>
- where DiscreteMeasure, F> : SpikeMerging {
- let mut μ = self.μ_prev;
- self.μ_prev = μ_base;
- μ.merge_spikes_fitness(merging,
- |μ̃| self.calculate_fit_simple(μ̃),
- |&v| v);
- μ.prune();
- μ
- }
-
- fn value_μ<'c, 'b : 'c>(&'c self, _μ : &'c DiscreteMeasure, F>)
- -> &'c DiscreteMeasure, F> {
- &self.μ_prev
- }
-}
-
-
-/// Abstraction of regularisation terms for [`generic_pointsource_fb_reg`].
-pub trait RegTerm
-: for<'a> Apply<&'a DiscreteMeasure, F>, Output = F> {
- /// Approximately solve the problem
- /// $$
- /// \min_{x ∈ ℝ^n} \frac{1}{2} x^⊤Ax - g^⊤ x + τ G(x)
- /// $$
- /// for $G$ depending on the trait implementation.
- ///
- /// The parameter `mA` is $A$. An estimate for its opeator norm should be provided in
- /// `mA_normest`. The initial iterate and output is `x`. The current main tolerance is `ε`.
- ///
- /// Returns the number of iterations taken.
- fn solve_findim(
- &self,
- mA : &DMatrix,
- g : &DVector,
- τ : F,
- x : &mut DVector,
- mA_normest : F,
- ε : 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
- /// 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(
- &self,
- d : &mut BTFN,
- τ : F,
- ε : F,
- skip_by_rough_check : bool,
- config : &FBGenericConfig,
- ) -> 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.
- fn verify_merge_candidate(
- &self,
- d : &mut BTFN,
- μ : &DiscreteMeasure, F>,
- τ : F,
- ε : F,
- config : &FBGenericConfig,
- ) -> bool
- where BT : BTSearch>,
- G : SupportGenerator,
- G::SupportType : Mapping,Codomain=F>
- + LocalAnalysis, N>;
-
- fn target_bounds(&self, τ : F, ε : F) -> Option>;
-
- /// Returns a scaling factor for the tolerance sequence.
- ///
- /// Typically this is the regularisation parameter.
- fn tolerance_scaling(&self) -> F;
+pub(crate) fn prune_with_stats(μ: &mut RNDM) -> usize {
+ let n_before_prune = μ.len();
+ μ.prune();
+ debug_assert!(μ.len() <= n_before_prune);
+ n_before_prune - μ.len()
}
#[replace_float_literals(F::cast_from(literal))]
-impl RegTerm for NonnegRadonRegTerm
-where Cube : P2Minimise, F> {
- fn solve_findim(
- &self,
- mA : &DMatrix,
- g : &DVector,
- τ : F,
- x : &mut DVector,
- mA_normest : F,
- ε : F,
- config : &FBGenericConfig
- ) -> 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)
- }
-
- #[inline]
- fn find_tolerance_violation(
- &self,
- d : &mut BTFN,
- τ : F,
- ε : F,
- skip_by_rough_check : bool,
- config : &FBGenericConfig,
- ) -> 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 refinement_tolerance = ε * config.refinement.tolerance_mult;
-
- // If preliminary check indicates that we are in bonds, and if it otherwise matches
- // the insertion strategy, skip insertion.
- if skip_by_rough_check && d.bounds().upper() <= keep_below {
- None
- } else {
- // If the rough check didn't indicate no insertion needed, find maximising point.
- d.maximise_above(maximise_above, refinement_tolerance, config.refinement.max_steps)
- .map(|(ξ, v_ξ)| (ξ, v_ξ, v_ξ <= keep_below))
- }
- }
-
- fn verify_merge_candidate(
- &self,
- d : &mut BTFN,
- μ : &DiscreteMeasure, F>,
- τ : F,
- ε : F,
- config : &FBGenericConfig,
- ) -> 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 keep_below = τα + merge_tolerance;
- let keep_supp_above = τα - merge_tolerance;
- let bnd = d.bounds();
-
- return (
- bnd.lower() >= keep_supp_above
- ||
- μ.iter_spikes().map(|&DeltaMeasure{ α : β, ref x }| {
- (β == 0.0) || d.apply(x) >= keep_supp_above
- }).all(std::convert::identity)
- ) && (
- 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(τα - ε, τα + ε))
- }
-
- fn tolerance_scaling(&self) -> F {
- self.α()
- }
+pub(crate) fn postprocess<
+ F: Float,
+ V: Euclidean + Clone,
+ A: GEMV, Codomain = V>,
+ D: DataTerm,
+ const N: usize,
+>(
+ mut μ: RNDM,
+ config: &FBGenericConfig,
+ dataterm: D,
+ opA: &A,
+ b: &V,
+) -> RNDM
+where
+ RNDM: SpikeMerging,
+ for<'a> &'a RNDM: Instance>,
+{
+ μ.merge_spikes_fitness(
+ config.final_merging_method(),
+ |μ̃| dataterm.calculate_fit_op(μ̃, opA, b),
+ |&v| v,
+ );
+ μ.prune();
+ μ
}
-#[replace_float_literals(F::cast_from(literal))]
-impl RegTerm for RadonRegTerm
-where Cube : P2Minimise, F> {
- fn solve_findim(
- &self,
- mA : &DMatrix,
- g : &DVector,
- τ : F,
- x : &mut DVector,
- mA_normest: F,
- ε : F,
- config : &FBGenericConfig
- ) -> 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)
- }
-
- fn find_tolerance_violation(
- &self,
- d : &mut BTFN,
- τ : F,
- ε : F,
- skip_by_rough_check : bool,
- config : &FBGenericConfig,
- ) -> 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 refinement_tolerance = ε * config.refinement.tolerance_mult;
-
- // If preliminary check indicates that we are in bonds, and if it otherwise matches
- // the insertion strategy, skip insertion.
- if skip_by_rough_check && Bounds(keep_above, keep_below).superset(&d.bounds()) {
- None
- } else {
- // If the rough check didn't indicate no insertion needed, find maximising point.
- let mx = d.maximise_above(maximise_above, refinement_tolerance,
- config.refinement.max_steps);
- let mi = d.minimise_below(minimise_below, refinement_tolerance,
- config.refinement.max_steps);
-
- match (mx, mi) {
- (None, None) => None,
- (Some((ξ, v_ξ)), None) => Some((ξ, v_ξ, keep_below >= v_ξ)),
- (None, Some((ζ, v_ζ))) => Some((ζ, v_ζ, keep_above <= v_ζ)),
- (Some((ξ, v_ξ)), Some((ζ, v_ζ))) => {
- if v_ξ - τα > τα - v_ζ {
- Some((ξ, v_ξ, keep_below >= v_ξ))
- } else {
- Some((ζ, v_ζ, keep_above <= v_ζ))
- }
- }
- }
- }
- }
-
- fn verify_merge_candidate(
- &self,
- d : &mut BTFN,
- μ : &DiscreteMeasure, F>,
- τ : F,
- ε : F,
- config : &FBGenericConfig,
- ) -> 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 keep_below = τα + merge_tolerance;
- let keep_above = -τα - merge_tolerance;
- let keep_supp_pos_above = τα - merge_tolerance;
- let keep_supp_neg_below = -τα + merge_tolerance;
- let bnd = d.bounds();
-
- return (
- (bnd.lower() >= keep_supp_pos_above && bnd.upper() <= keep_supp_neg_below)
- ||
- μ.iter_spikes().map(|&DeltaMeasure{ α : β, ref x }| {
- use std::cmp::Ordering::*;
- 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
- ||
- d.has_upper_bound(keep_below, refinement_tolerance,
- config.refinement.max_steps)
- ) && (
- 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(-τα - ε, τα + ε))
- }
-
- fn tolerance_scaling(&self) -> F {
- self.α()
- }
-}
-
-
-/// Generic implementation of [`pointsource_fb_reg`].
+/// Iteratively solve the pointsource localisation problem using forward-backward splitting.
///
-/// The method can be specialised to even primal-dual proximal splitting through the
-/// [`FBSpecialisation`] parameter `specialisation`.
-/// The settings in `config` have their [respective documentation](FBGenericConfig). `opA` is the
+/// The settings in `config` have their [respective documentation](FBConfig). `opA` is the
/// forward operator $A$, $b$ the observable, and $\lambda$ the regularisation weight.
/// The operator `op𝒟` is used for forming the proximal term. Typically it is a convolution
/// operator. Finally, the `iterator` is an outer loop verbosity and iteration count control
/// as documented in [`alg_tools::iterate`].
///
+/// For details on the mathematical formulation, see the [module level](self) documentation.
+///
/// The implementation relies on [`alg_tools::bisection_tree::BTFN`] presentations of
/// sums of simple functions usign bisection trees, and the related
/// [`alg_tools::bisection_tree::Aggregator`]s, to efficiently search for component functions
@@ -729,233 +170,103 @@
///
/// Returns the final iterate.
#[replace_float_literals(F::cast_from(literal))]
-pub fn generic_pointsource_fb_reg<
- 'a, F, I, A, GA, 𝒟, BTA, G𝒟, S, K, Spec, Reg, const N : usize
->(
- opA : &'a A,
- reg : Reg,
- op𝒟 : &'a 𝒟,
- mut τ : F,
- config : &FBGenericConfig,
- iterator : I,
- mut plotter : SeqPlotter,
- mut residual : A::Observable,
- mut specialisation : Spec
-) -> DiscreteMeasure, F>
-where F : Float + ToNalgebraRealField,
- I : AlgIteratorFactory>,
- Spec : FBSpecialisation,
- A::Observable : std::ops::MulAssign,
- GA : SupportGenerator + Clone,
- A : ForwardModel, F, PreadjointCodomain = BTFN>
- + Lipschitz<𝒟, FloatType=F>,
- BTA : BTSearch>,
- G𝒟 : SupportGenerator + Clone,
- 𝒟 : DiscreteMeasureOp, F, PreCodomain = PreBTFN>,
- 𝒟::Codomain : RealMapping,
- S: RealMapping + LocalAnalysis, N>,
- K: RealMapping + LocalAnalysis, N>,
- BTNodeLookup: BTNode, N>,
- PlotLookup : Plotting,
- DiscreteMeasure, F> : SpikeMerging,
- Reg : RegTerm {
-
+pub fn pointsource_fb_reg(
+ opA: &A,
+ b: &A::Observable,
+ reg: Reg,
+ prox_penalty: &P,
+ fbconfig: &FBConfig,
+ iterator: I,
+ mut plotter: SeqPlotter,
+) -> RNDM
+where
+ F: Float + ToNalgebraRealField,
+ I: AlgIteratorFactory>,
+ for<'b> &'b A::Observable: std::ops::Neg