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