Mon, 06 Jan 2025 11:32:57 -0500
Factor fix
0 | 1 | /*! |
2 | Solver for the point source localisation problem using a forward-backward splitting method. | |
3 | ||
4 | This corresponds to the manuscript | |
5 | ||
13
bdc57366d4f5
arXiv links, README beautification
Tuomo Valkonen <tuomov@iki.fi>
parents:
8
diff
changeset
|
6 | * Valkonen T. - _Proximal methods for point source localisation_, |
bdc57366d4f5
arXiv links, README beautification
Tuomo Valkonen <tuomov@iki.fi>
parents:
8
diff
changeset
|
7 | [arXiv:2212.02991](https://arxiv.org/abs/2212.02991). |
0 | 8 | |
35 | 9 | The main routine is [`pointsource_fb_reg`]. |
0 | 10 | |
11 | ## Problem | |
12 | ||
13 | <p> | |
14 | Our objective is to solve | |
15 | $$ | |
16 | \min_{μ ∈ ℳ(Ω)}~ F_0(Aμ-b) + α \|μ\|_{ℳ(Ω)} + δ_{≥ 0}(μ), | |
17 | $$ | |
18 | where $F_0(y)=\frac{1}{2}\|y\|_2^2$ and the forward operator $A \in 𝕃(ℳ(Ω); ℝ^n)$. | |
19 | </p> | |
20 | ||
21 | ## Approach | |
22 | ||
23 | <p> | |
24 | As documented in more detail in the paper, on each step we approximately solve | |
25 | $$ | |
26 | \min_{μ ∈ ℳ(Ω)}~ F(x) + α \|μ\|_{ℳ(Ω)} + δ_{≥ 0}(x) + \frac{1}{2}\|μ-μ^k|_𝒟^2, | |
27 | $$ | |
28 | where $𝒟: 𝕃(ℳ(Ω); C_c(Ω))$ is typically a convolution operator. | |
29 | </p> | |
30 | ||
31 | ## Finite-dimensional subproblems. | |
32 | ||
33 | With $C$ a projection from [`DiscreteMeasure`] to the weights, and $x^k$ such that $x^k=Cμ^k$, we | |
34 | form the discretised linearised inner problem | |
35 | <p> | |
36 | $$ | |
37 | \min_{x ∈ ℝ^n}~ τ\bigl(F(Cx^k) + [C^*∇F(Cx^k)]^⊤(x-x^k) + α {\vec 1}^⊤ x\bigr) | |
38 | + δ_{≥ 0}(x) + \frac{1}{2}\|x-x^k\|_{C^*𝒟C}^2, | |
39 | $$ | |
40 | equivalently | |
41 | $$ | |
42 | \begin{aligned} | |
43 | \min_x~ & τF(Cx^k) - τ[C^*∇F(Cx^k)]^⊤x^k + \frac{1}{2} (x^k)^⊤ C^*𝒟C x^k | |
44 | \\ | |
45 | & | |
46 | - [C^*𝒟C x^k - τC^*∇F(Cx^k)]^⊤ x | |
47 | \\ | |
48 | & | |
49 | + \frac{1}{2} x^⊤ C^*𝒟C x | |
50 | + τα {\vec 1}^⊤ x + δ_{≥ 0}(x), | |
51 | \end{aligned} | |
52 | $$ | |
53 | In other words, we obtain the quadratic non-negativity constrained problem | |
54 | $$ | |
55 | \min_{x ∈ ℝ^n}~ \frac{1}{2} x^⊤ Ã x - b̃^⊤ x + c + τα {\vec 1}^⊤ x + δ_{≥ 0}(x). | |
56 | $$ | |
57 | where | |
58 | $$ | |
59 | \begin{aligned} | |
60 | Ã & = C^*𝒟C, | |
61 | \\ | |
62 | g̃ & = C^*𝒟C x^k - τ C^*∇F(Cx^k) | |
63 | = C^* 𝒟 μ^k - τ C^*A^*(Aμ^k - b) | |
64 | \\ | |
65 | c & = τ F(Cx^k) - τ[C^*∇F(Cx^k)]^⊤x^k + \frac{1}{2} (x^k)^⊤ C^*𝒟C x^k | |
66 | \\ | |
67 | & | |
68 | = \frac{τ}{2} \|Aμ^k-b\|^2 - τ[Aμ^k-b]^⊤Aμ^k + \frac{1}{2} \|μ_k\|_{𝒟}^2 | |
69 | \\ | |
70 | & | |
71 | = -\frac{τ}{2} \|Aμ^k-b\|^2 + τ[Aμ^k-b]^⊤ b + \frac{1}{2} \|μ_k\|_{𝒟}^2. | |
72 | \end{aligned} | |
73 | $$ | |
74 | </p> | |
75 | ||
35 | 76 | We solve this with either SSN or FB as determined by |
0 | 77 | [`InnerSettings`] in [`FBGenericConfig::inner`]. |
78 | */ | |
79 | ||
80 | use numeric_literals::replace_float_literals; | |
81 | use serde::{Serialize, Deserialize}; | |
82 | use colored::Colorize; | |
32 | 83 | use nalgebra::DVector; |
0 | 84 | |
85 | use alg_tools::iterate::{ | |
86 | AlgIteratorFactory, | |
35 | 87 | AlgIteratorIteration, |
88 | AlgIterator, | |
0 | 89 | }; |
90 | use alg_tools::euclidean::Euclidean; | |
35 | 91 | use alg_tools::linops::{Mapping, GEMV}; |
0 | 92 | use alg_tools::sets::Cube; |
93 | use alg_tools::loc::Loc; | |
94 | use alg_tools::bisection_tree::{ | |
95 | BTFN, | |
96 | PreBTFN, | |
97 | Bounds, | |
98 | BTNodeLookup, | |
99 | BTNode, | |
100 | BTSearch, | |
101 | P2Minimise, | |
102 | SupportGenerator, | |
103 | LocalAnalysis, | |
32 | 104 | BothGenerators, |
0 | 105 | }; |
106 | use alg_tools::mapping::RealMapping; | |
107 | use alg_tools::nalgebra_support::ToNalgebraRealField; | |
35 | 108 | use alg_tools::instance::Instance; |
109 | use alg_tools::norms::Linfinity; | |
0 | 110 | |
111 | use crate::types::*; | |
112 | use crate::measures::{ | |
113 | DiscreteMeasure, | |
35 | 114 | RNDM, |
0 | 115 | DeltaMeasure, |
35 | 116 | Radon, |
0 | 117 | }; |
118 | use crate::measures::merging::{ | |
119 | SpikeMergingMethod, | |
120 | SpikeMerging, | |
121 | }; | |
35 | 122 | use crate::forward_model::{ |
123 | ForwardModel, | |
124 | AdjointProductBoundedBy | |
125 | }; | |
32 | 126 | use crate::seminorms::DiscreteMeasureOp; |
0 | 127 | use crate::subproblem::{ |
128 | InnerSettings, | |
129 | InnerMethod, | |
130 | }; | |
131 | use crate::tolerance::Tolerance; | |
132 | use crate::plot::{ | |
133 | SeqPlotter, | |
134 | Plotting, | |
135 | PlotLookup | |
136 | }; | |
32 | 137 | use crate::regularisation::RegTerm; |
138 | use crate::dataterm::{ | |
139 | calculate_residual, | |
140 | L2Squared, | |
141 | DataTerm, | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
142 | }; |
0 | 143 | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
144 | /// Settings for [`pointsource_fb_reg`]. |
0 | 145 | #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] |
146 | #[serde(default)] | |
147 | pub struct FBConfig<F : Float> { | |
148 | /// Step length scaling | |
149 | pub τ0 : F, | |
150 | /// Generic parameters | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
151 | pub generic : FBGenericConfig<F>, |
0 | 152 | } |
153 | ||
35 | 154 | /// Settings for the solution of the stepwise optimality condition. |
0 | 155 | #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] |
156 | #[serde(default)] | |
157 | pub struct FBGenericConfig<F : Float> { | |
158 | /// Tolerance for point insertion. | |
159 | pub tolerance : Tolerance<F>, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
160 | |
0 | 161 | /// Stop looking for predual maximum (where to isert a new point) below |
162 | /// `tolerance` multiplied by this factor. | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
163 | /// |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
164 | /// Not used by [`super::radon_fb`]. |
0 | 165 | pub insertion_cutoff_factor : F, |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
166 | |
0 | 167 | /// Settings for branch and bound refinement when looking for predual maxima |
168 | pub refinement : RefinementSettings<F>, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
169 | |
0 | 170 | /// Maximum insertions within each outer iteration |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
171 | /// |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
172 | /// Not used by [`super::radon_fb`]. |
0 | 173 | pub max_insertions : usize, |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
174 | |
0 | 175 | /// Pair `(n, m)` for maximum insertions `m` on first `n` iterations. |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
176 | /// |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
177 | /// Not used by [`super::radon_fb`]. |
0 | 178 | pub bootstrap_insertions : Option<(usize, usize)>, |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
179 | |
0 | 180 | /// Inner method settings |
181 | pub inner : InnerSettings<F>, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
182 | |
0 | 183 | /// Spike merging method |
184 | pub merging : SpikeMergingMethod<F>, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
185 | |
0 | 186 | /// Tolerance multiplier for merges |
187 | pub merge_tolerance_mult : F, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
188 | |
0 | 189 | /// Spike merging method after the last step |
190 | pub final_merging : SpikeMergingMethod<F>, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
191 | |
0 | 192 | /// Iterations between merging heuristic tries |
193 | pub merge_every : usize, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
194 | |
35 | 195 | // /// Save $μ$ for postprocessing optimisation |
196 | // pub postprocessing : bool | |
0 | 197 | } |
198 | ||
199 | #[replace_float_literals(F::cast_from(literal))] | |
200 | impl<F : Float> Default for FBConfig<F> { | |
201 | fn default() -> Self { | |
202 | FBConfig { | |
203 | τ0 : 0.99, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
204 | generic : Default::default(), |
0 | 205 | } |
206 | } | |
207 | } | |
208 | ||
209 | #[replace_float_literals(F::cast_from(literal))] | |
210 | impl<F : Float> Default for FBGenericConfig<F> { | |
211 | fn default() -> Self { | |
212 | FBGenericConfig { | |
213 | tolerance : Default::default(), | |
214 | insertion_cutoff_factor : 1.0, | |
215 | refinement : Default::default(), | |
216 | max_insertions : 100, | |
217 | //bootstrap_insertions : None, | |
218 | bootstrap_insertions : Some((10, 1)), | |
219 | inner : InnerSettings { | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
220 | method : InnerMethod::Default, |
0 | 221 | .. Default::default() |
222 | }, | |
223 | merging : SpikeMergingMethod::None, | |
224 | //merging : Default::default(), | |
225 | final_merging : Default::default(), | |
226 | merge_every : 10, | |
227 | merge_tolerance_mult : 2.0, | |
35 | 228 | // postprocessing : false, |
0 | 229 | } |
230 | } | |
231 | } | |
232 | ||
35 | 233 | impl<F : Float> FBGenericConfig<F> { |
234 | /// Check if merging should be attempted this iteration | |
235 | pub fn merge_now<I : AlgIterator>(&self, state : &AlgIteratorIteration<I>) -> bool { | |
236 | state.iteration() % self.merge_every == 0 | |
237 | } | |
238 | } | |
239 | ||
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
240 | /// TODO: document. |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
241 | /// `μ_base + ν_delta` is the base point, where `μ` and `μ_base` are assumed to have the same spike |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
242 | /// locations, while `ν_delta` may have different locations. |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
243 | #[replace_float_literals(F::cast_from(literal))] |
32 | 244 | pub(crate) fn insert_and_reweigh< |
35 | 245 | 'a, F, GA, 𝒟, BTA, G𝒟, S, K, Reg, I, const N : usize |
32 | 246 | >( |
35 | 247 | μ : &mut RNDM<F, N>, |
248 | τv : &BTFN<F, GA, BTA, N>, | |
249 | μ_base : &RNDM<F, N>, | |
250 | ν_delta: Option<&RNDM<F, N>>, | |
32 | 251 | op𝒟 : &'a 𝒟, |
252 | op𝒟norm : F, | |
253 | τ : F, | |
254 | ε : F, | |
255 | config : &FBGenericConfig<F>, | |
256 | reg : &Reg, | |
35 | 257 | state : &AlgIteratorIteration<I>, |
32 | 258 | stats : &mut IterInfo<F, N>, |
259 | ) -> (BTFN<F, BothGenerators<GA, G𝒟>, BTA, N>, bool) | |
260 | where F : Float + ToNalgebraRealField, | |
261 | GA : SupportGenerator<F, N, SupportType = S, Id = usize> + Clone, | |
262 | BTA : BTSearch<F, N, Data=usize, Agg=Bounds<F>>, | |
263 | G𝒟 : SupportGenerator<F, N, SupportType = K, Id = usize> + Clone, | |
264 | 𝒟 : DiscreteMeasureOp<Loc<F, N>, F, PreCodomain = PreBTFN<F, G𝒟, N>>, | |
265 | 𝒟::Codomain : RealMapping<F, N>, | |
266 | S: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, | |
267 | K: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, | |
268 | BTNodeLookup: BTNode<F, usize, Bounds<F>, N>, | |
269 | Reg : RegTerm<F, N>, | |
35 | 270 | I : AlgIterator { |
32 | 271 | |
272 | // Maximum insertion count and measure difference calculation depend on insertion style. | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
273 | let (max_insertions, warn_insertions) = match (state.iteration(), config.bootstrap_insertions) { |
32 | 274 | (i, Some((l, k))) if i <= l => (k, false), |
275 | _ => (config.max_insertions, !state.is_quiet()), | |
276 | }; | |
277 | ||
35 | 278 | let ω0 = match ν_delta { |
279 | None => op𝒟.apply(μ_base), | |
280 | Some(ν) => op𝒟.apply(μ_base + ν), | |
281 | }; | |
32 | 282 | |
283 | // Add points to support until within error tolerance or maximum insertion count reached. | |
284 | let mut count = 0; | |
285 | let (within_tolerances, d) = 'insertion: loop { | |
286 | if μ.len() > 0 { | |
287 | // Form finite-dimensional subproblem. The subproblem references to the original μ^k | |
288 | // from the beginning of the iteration are all contained in the immutable c and g. | |
35 | 289 | // TODO: observe negation of -τv after switch from minus_τv: finite-dimensional |
290 | // problems have not yet been updated to sign change. | |
32 | 291 | let à = op𝒟.findim_matrix(μ.iter_locations()); |
292 | let g̃ = DVector::from_iterator(μ.len(), | |
293 | μ.iter_locations() | |
35 | 294 | .map(|ζ| ω0.apply(ζ) - τv.apply(ζ)) |
32 | 295 | .map(F::to_nalgebra_mixed)); |
296 | let mut x = μ.masses_dvector(); | |
297 | ||
298 | // The gradient of the forward component of the inner objective is C^*𝒟Cx - g̃. | |
299 | // We have |C^*𝒟Cx|_2 = sup_{|z|_2 ≤ 1} ⟨z, C^*𝒟Cx⟩ = sup_{|z|_2 ≤ 1} ⟨Cz|𝒟Cx⟩ | |
300 | // ≤ sup_{|z|_2 ≤ 1} |Cz|_ℳ |𝒟Cx|_∞ ≤ sup_{|z|_2 ≤ 1} |Cz|_ℳ |𝒟| |Cx|_ℳ | |
301 | // ≤ sup_{|z|_2 ≤ 1} |z|_1 |𝒟| |x|_1 ≤ sup_{|z|_2 ≤ 1} n |z|_2 |𝒟| |x|_2 | |
302 | // = n |𝒟| |x|_2, where n is the number of points. Therefore | |
303 | let Ã_normest = op𝒟norm * F::cast_from(μ.len()); | |
304 | ||
305 | // Solve finite-dimensional subproblem. | |
306 | stats.inner_iters += reg.solve_findim(&Ã, &g̃, τ, &mut x, Ã_normest, ε, config); | |
307 | ||
308 | // Update masses of μ based on solution of finite-dimensional subproblem. | |
309 | μ.set_masses_dvector(&x); | |
310 | } | |
311 | ||
35 | 312 | // Form d = τv + 𝒟μ - ω0 = τv + 𝒟(μ - μ^k) for checking the proximate optimality |
32 | 313 | // conditions in the predual space, and finding new points for insertion, if necessary. |
35 | 314 | let mut d = τv + match ν_delta { |
315 | None => op𝒟.preapply(μ.sub_matching(μ_base)), | |
316 | Some(ν) => op𝒟.preapply(μ.sub_matching(μ_base) - ν) | |
317 | }; | |
32 | 318 | |
319 | // If no merging heuristic is used, let's be more conservative about spike insertion, | |
320 | // and skip it after first round. If merging is done, being more greedy about spike | |
321 | // insertion also seems to improve performance. | |
322 | let skip_by_rough_check = if let SpikeMergingMethod::None = config.merging { | |
323 | false | |
324 | } else { | |
325 | count > 0 | |
326 | }; | |
327 | ||
328 | // Find a spike to insert, if needed | |
329 | let (ξ, _v_ξ, in_bounds) = match reg.find_tolerance_violation( | |
330 | &mut d, τ, ε, skip_by_rough_check, config | |
331 | ) { | |
332 | None => break 'insertion (true, d), | |
333 | Some(res) => res, | |
334 | }; | |
335 | ||
336 | // Break if maximum insertion count reached | |
337 | if count >= max_insertions { | |
338 | break 'insertion (in_bounds, d) | |
339 | } | |
340 | ||
341 | // No point in optimising the weight here; the finite-dimensional algorithm is fast. | |
342 | *μ += DeltaMeasure { x : ξ, α : 0.0 }; | |
343 | count += 1; | |
35 | 344 | stats.inserted += 1; |
32 | 345 | }; |
346 | ||
347 | if !within_tolerances && warn_insertions { | |
348 | // Complain (but continue) if we failed to get within tolerances | |
349 | // by inserting more points. | |
350 | let err = format!("Maximum insertions reached without achieving \ | |
351 | subproblem solution tolerance"); | |
352 | println!("{}", err.red()); | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
353 | } |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
354 | |
32 | 355 | (d, within_tolerances) |
356 | } | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
357 | |
35 | 358 | pub(crate) fn prune_with_stats<F : Float, const N : usize>( |
359 | μ : &mut RNDM<F, N>, | |
360 | ) -> usize { | |
32 | 361 | let n_before_prune = μ.len(); |
362 | μ.prune(); | |
363 | debug_assert!(μ.len() <= n_before_prune); | |
35 | 364 | n_before_prune - μ.len() |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
365 | } |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
366 | |
32 | 367 | #[replace_float_literals(F::cast_from(literal))] |
368 | pub(crate) fn postprocess< | |
369 | F : Float, | |
370 | V : Euclidean<F> + Clone, | |
35 | 371 | A : GEMV<F, RNDM<F, N>, Codomain = V>, |
32 | 372 | D : DataTerm<F, V, N>, |
373 | const N : usize | |
374 | > ( | |
35 | 375 | mut μ : RNDM<F, N>, |
32 | 376 | config : &FBGenericConfig<F>, |
377 | dataterm : D, | |
378 | opA : &A, | |
379 | b : &V, | |
35 | 380 | ) -> RNDM<F, N> |
381 | where | |
382 | RNDM<F, N> : SpikeMerging<F>, | |
383 | for<'a> &'a RNDM<F, N> : Instance<RNDM<F, N>>, | |
384 | { | |
32 | 385 | μ.merge_spikes_fitness(config.merging, |
386 | |μ̃| dataterm.calculate_fit_op(μ̃, opA, b), | |
387 | |&v| v); | |
388 | μ.prune(); | |
389 | μ | |
390 | } | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
391 | |
32 | 392 | /// Iteratively solve the pointsource localisation problem using forward-backward splitting. |
0 | 393 | /// |
32 | 394 | /// The settings in `config` have their [respective documentation](FBConfig). `opA` is the |
0 | 395 | /// forward operator $A$, $b$ the observable, and $\lambda$ the regularisation weight. |
396 | /// The operator `op𝒟` is used for forming the proximal term. Typically it is a convolution | |
397 | /// operator. Finally, the `iterator` is an outer loop verbosity and iteration count control | |
398 | /// as documented in [`alg_tools::iterate`]. | |
399 | /// | |
32 | 400 | /// For details on the mathematical formulation, see the [module level](self) documentation. |
401 | /// | |
0 | 402 | /// The implementation relies on [`alg_tools::bisection_tree::BTFN`] presentations of |
403 | /// sums of simple functions usign bisection trees, and the related | |
404 | /// [`alg_tools::bisection_tree::Aggregator`]s, to efficiently search for component functions | |
405 | /// active at a specific points, and to maximise their sums. Through the implementation of the | |
406 | /// [`alg_tools::bisection_tree::BT`] bisection trees, it also relies on the copy-on-write features | |
407 | /// of [`std::sync::Arc`] to only update relevant parts of the bisection tree when adding functions. | |
408 | /// | |
409 | /// Returns the final iterate. | |
410 | #[replace_float_literals(F::cast_from(literal))] | |
32 | 411 | pub fn pointsource_fb_reg< |
412 | 'a, F, I, A, GA, 𝒟, BTA, G𝒟, S, K, Reg, const N : usize | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
413 | >( |
0 | 414 | opA : &'a A, |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
415 | b : &A::Observable, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
416 | reg : Reg, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
417 | op𝒟 : &'a 𝒟, |
32 | 418 | fbconfig : &FBConfig<F>, |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
419 | iterator : I, |
32 | 420 | mut plotter : SeqPlotter<F, N>, |
35 | 421 | ) -> RNDM<F, N> |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
422 | where F : Float + ToNalgebraRealField, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
423 | I : AlgIteratorFactory<IterInfo<F, N>>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
424 | for<'b> &'b A::Observable : std::ops::Neg<Output=A::Observable>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
425 | GA : SupportGenerator<F, N, SupportType = S, Id = usize> + Clone, |
35 | 426 | A : ForwardModel<RNDM<F, N>, F, PreadjointCodomain = BTFN<F, GA, BTA, N>> |
427 | + AdjointProductBoundedBy<RNDM<F, N>, 𝒟, FloatType=F>, | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
428 | BTA : BTSearch<F, N, Data=usize, Agg=Bounds<F>>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
429 | G𝒟 : SupportGenerator<F, N, SupportType = K, Id = usize> + Clone, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
430 | 𝒟 : DiscreteMeasureOp<Loc<F, N>, F, PreCodomain = PreBTFN<F, G𝒟, N>>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
431 | 𝒟::Codomain : RealMapping<F, N>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
432 | S: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
433 | K: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
434 | BTNodeLookup: BTNode<F, usize, Bounds<F>, N>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
435 | Cube<F, N>: P2Minimise<Loc<F, N>, F>, |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
436 | PlotLookup : Plotting<N>, |
35 | 437 | RNDM<F, N> : SpikeMerging<F>, |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
438 | Reg : RegTerm<F, N> { |
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
439 | |
32 | 440 | // Set up parameters |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
441 | let config = &fbconfig.generic; |
35 | 442 | let op𝒟norm = op𝒟.opnorm_bound(Radon, Linfinity); |
443 | let τ = fbconfig.τ0/opA.adjoint_product_bound(&op𝒟).unwrap(); | |
32 | 444 | // We multiply tolerance by τ for FB since our subproblems depending on tolerances are scaled |
445 | // by τ compared to the conditional gradient approach. | |
446 | let tolerance = config.tolerance * τ * reg.tolerance_scaling(); | |
447 | let mut ε = tolerance.initial(); | |
448 | ||
449 | // Initialise iterates | |
450 | let mut μ = DiscreteMeasure::new(); | |
451 | let mut residual = -b; | |
35 | 452 | |
453 | // Statistics | |
454 | let full_stats = |residual : &A::Observable, | |
455 | μ : &RNDM<F, N>, | |
456 | ε, stats| IterInfo { | |
457 | value : residual.norm2_squared_div2() + reg.apply(μ), | |
458 | n_spikes : μ.len(), | |
459 | ε, | |
460 | //postprocessing: config.postprocessing.then(|| μ.clone()), | |
461 | .. stats | |
462 | }; | |
32 | 463 | let mut stats = IterInfo::new(); |
464 | ||
465 | // Run the algorithm | |
35 | 466 | for state in iterator.iter_init(|| full_stats(&residual, &μ, ε, stats.clone())) { |
32 | 467 | // Calculate smooth part of surrogate model. |
35 | 468 | let τv = opA.preadjoint().apply(residual * τ); |
32 | 469 | |
470 | // Save current base point | |
471 | let μ_base = μ.clone(); | |
472 | ||
473 | // Insert and reweigh | |
35 | 474 | let (d, _within_tolerances) = insert_and_reweigh( |
475 | &mut μ, &τv, &μ_base, None, | |
32 | 476 | op𝒟, op𝒟norm, |
477 | τ, ε, | |
35 | 478 | config, ®, &state, &mut stats |
32 | 479 | ); |
480 | ||
481 | // Prune and possibly merge spikes | |
35 | 482 | if config.merge_now(&state) { |
483 | stats.merged += μ.merge_spikes(config.merging, |μ_candidate| { | |
484 | let mut d = &τv + op𝒟.preapply(μ_candidate.sub_matching(&μ_base)); | |
485 | reg.verify_merge_candidate(&mut d, μ_candidate, τ, ε, &config) | |
486 | }); | |
487 | } | |
488 | stats.pruned += prune_with_stats(&mut μ); | |
32 | 489 | |
490 | // Update residual | |
491 | residual = calculate_residual(&μ, opA, b); | |
492 | ||
35 | 493 | let iter = state.iteration(); |
32 | 494 | stats.this_iters += 1; |
495 | ||
35 | 496 | // Give statistics if needed |
32 | 497 | state.if_verbose(|| { |
35 | 498 | plotter.plot_spikes(iter, Some(&d), Some(&τv), &μ); |
499 | full_stats(&residual, &μ, ε, std::mem::replace(&mut stats, IterInfo::new())) | |
500 | }); | |
501 | ||
502 | // Update main tolerance for next iteration | |
503 | ε = tolerance.update(ε, iter); | |
504 | } | |
32 | 505 | |
506 | postprocess(μ, config, L2Squared, opA, b) | |
507 | } | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
508 | |
32 | 509 | /// Iteratively solve the pointsource localisation problem using inertial forward-backward splitting. |
510 | /// | |
511 | /// The settings in `config` have their [respective documentation](FBConfig). `opA` is the | |
512 | /// forward operator $A$, $b$ the observable, and $\lambda$ the regularisation weight. | |
513 | /// The operator `op𝒟` is used for forming the proximal term. Typically it is a convolution | |
514 | /// operator. Finally, the `iterator` is an outer loop verbosity and iteration count control | |
515 | /// as documented in [`alg_tools::iterate`]. | |
516 | /// | |
517 | /// For details on the mathematical formulation, see the [module level](self) documentation. | |
518 | /// | |
519 | /// The implementation relies on [`alg_tools::bisection_tree::BTFN`] presentations of | |
520 | /// sums of simple functions usign bisection trees, and the related | |
521 | /// [`alg_tools::bisection_tree::Aggregator`]s, to efficiently search for component functions | |
522 | /// active at a specific points, and to maximise their sums. Through the implementation of the | |
523 | /// [`alg_tools::bisection_tree::BT`] bisection trees, it also relies on the copy-on-write features | |
524 | /// of [`std::sync::Arc`] to only update relevant parts of the bisection tree when adding functions. | |
525 | /// | |
526 | /// Returns the final iterate. | |
527 | #[replace_float_literals(F::cast_from(literal))] | |
528 | pub fn pointsource_fista_reg< | |
529 | 'a, F, I, A, GA, 𝒟, BTA, G𝒟, S, K, Reg, const N : usize | |
530 | >( | |
531 | opA : &'a A, | |
532 | b : &A::Observable, | |
533 | reg : Reg, | |
534 | op𝒟 : &'a 𝒟, | |
535 | fbconfig : &FBConfig<F>, | |
536 | iterator : I, | |
537 | mut plotter : SeqPlotter<F, N>, | |
35 | 538 | ) -> RNDM<F, N> |
32 | 539 | where F : Float + ToNalgebraRealField, |
540 | I : AlgIteratorFactory<IterInfo<F, N>>, | |
541 | for<'b> &'b A::Observable : std::ops::Neg<Output=A::Observable>, | |
542 | GA : SupportGenerator<F, N, SupportType = S, Id = usize> + Clone, | |
35 | 543 | A : ForwardModel<RNDM<F, N>, F, PreadjointCodomain = BTFN<F, GA, BTA, N>> |
544 | + AdjointProductBoundedBy<RNDM<F, N>, 𝒟, FloatType=F>, | |
32 | 545 | BTA : BTSearch<F, N, Data=usize, Agg=Bounds<F>>, |
546 | G𝒟 : SupportGenerator<F, N, SupportType = K, Id = usize> + Clone, | |
547 | 𝒟 : DiscreteMeasureOp<Loc<F, N>, F, PreCodomain = PreBTFN<F, G𝒟, N>>, | |
548 | 𝒟::Codomain : RealMapping<F, N>, | |
549 | S: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, | |
550 | K: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, | |
551 | BTNodeLookup: BTNode<F, usize, Bounds<F>, N>, | |
552 | Cube<F, N>: P2Minimise<Loc<F, N>, F>, | |
553 | PlotLookup : Plotting<N>, | |
35 | 554 | RNDM<F, N> : SpikeMerging<F>, |
32 | 555 | Reg : RegTerm<F, N> { |
556 | ||
557 | // Set up parameters | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
558 | let config = &fbconfig.generic; |
35 | 559 | let op𝒟norm = op𝒟.opnorm_bound(Radon, Linfinity); |
560 | let τ = fbconfig.τ0/opA.adjoint_product_bound(&op𝒟).unwrap(); | |
32 | 561 | let mut λ = 1.0; |
562 | // We multiply tolerance by τ for FB since our subproblems depending on tolerances are scaled | |
563 | // by τ compared to the conditional gradient approach. | |
564 | let tolerance = config.tolerance * τ * reg.tolerance_scaling(); | |
565 | let mut ε = tolerance.initial(); | |
566 | ||
567 | // Initialise iterates | |
568 | let mut μ = DiscreteMeasure::new(); | |
569 | let mut μ_prev = DiscreteMeasure::new(); | |
570 | let mut residual = -b; | |
35 | 571 | let mut warned_merging = false; |
572 | ||
573 | // Statistics | |
574 | let full_stats = |ν : &RNDM<F, N>, ε, stats| IterInfo { | |
575 | value : L2Squared.calculate_fit_op(ν, opA, b) + reg.apply(ν), | |
576 | n_spikes : ν.len(), | |
577 | ε, | |
578 | // postprocessing: config.postprocessing.then(|| ν.clone()), | |
579 | .. stats | |
580 | }; | |
32 | 581 | let mut stats = IterInfo::new(); |
582 | ||
583 | // Run the algorithm | |
35 | 584 | for state in iterator.iter_init(|| full_stats(&μ, ε, stats.clone())) { |
32 | 585 | // Calculate smooth part of surrogate model. |
35 | 586 | let τv = opA.preadjoint().apply(residual * τ); |
32 | 587 | |
588 | // Save current base point | |
589 | let μ_base = μ.clone(); | |
590 | ||
591 | // Insert new spikes and reweigh | |
35 | 592 | let (d, _within_tolerances) = insert_and_reweigh( |
593 | &mut μ, &τv, &μ_base, None, | |
32 | 594 | op𝒟, op𝒟norm, |
595 | τ, ε, | |
35 | 596 | config, ®, &state, &mut stats |
32 | 597 | ); |
598 | ||
599 | // (Do not) merge spikes. | |
35 | 600 | if config.merge_now(&state) { |
32 | 601 | match config.merging { |
602 | SpikeMergingMethod::None => { }, | |
603 | _ => if !warned_merging { | |
604 | let err = format!("Merging not supported for μFISTA"); | |
605 | println!("{}", err.red()); | |
606 | warned_merging = true; | |
607 | } | |
608 | } | |
609 | } | |
610 | ||
611 | // Update inertial prameters | |
612 | let λ_prev = λ; | |
613 | λ = 2.0 * λ_prev / ( λ_prev + (4.0 + λ_prev * λ_prev).sqrt() ); | |
614 | let θ = λ / λ_prev - λ; | |
615 | ||
616 | // Perform inertial update on μ. | |
617 | // This computes μ ← (1 + θ) * μ - θ * μ_prev, pruning spikes where both μ | |
618 | // and μ_prev have zero weight. Since both have weights from the finite-dimensional | |
619 | // subproblem with a proximal projection step, this is likely to happen when the | |
620 | // spike is not needed. A copy of the pruned μ without artithmetic performed is | |
621 | // stored in μ_prev. | |
622 | let n_before_prune = μ.len(); | |
623 | μ.pruning_sub(1.0 + θ, θ, &mut μ_prev); | |
624 | debug_assert!(μ.len() <= n_before_prune); | |
625 | stats.pruned += n_before_prune - μ.len(); | |
626 | ||
627 | // Update residual | |
628 | residual = calculate_residual(&μ, opA, b); | |
629 | ||
35 | 630 | let iter = state.iteration(); |
32 | 631 | stats.this_iters += 1; |
632 | ||
35 | 633 | // Give statistics if needed |
32 | 634 | state.if_verbose(|| { |
35 | 635 | plotter.plot_spikes(iter, Some(&d), Some(&τv), &μ_prev); |
636 | full_stats(&μ_prev, ε, std::mem::replace(&mut stats, IterInfo::new())) | |
637 | }); | |
638 | ||
639 | // Update main tolerance for next iteration | |
640 | ε = tolerance.update(ε, iter); | |
641 | } | |
32 | 642 | |
643 | postprocess(μ_prev, config, L2Squared, opA, b) | |
24
d29d1fcf5423
Support arbitrary regularisation terms; implement non-positivity-constrained regularisation.
Tuomo Valkonen <tuomov@iki.fi>
parents:
13
diff
changeset
|
644 | } |