Tue, 31 Dec 2024 09:25:45 -0500
New version of sliding.
32 | 1 | /*! |
2 | Solver for the point source localisation problem using a sliding | |
3 | forward-backward splitting method. | |
4 | */ | |
5 | ||
6 | use numeric_literals::replace_float_literals; | |
7 | use serde::{Serialize, Deserialize}; | |
8 | //use colored::Colorize; | |
9 | //use nalgebra::{DVector, DMatrix}; | |
10 | use itertools::izip; | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
11 | use std::iter::Iterator; |
32 | 12 | |
35 | 13 | use alg_tools::iterate::AlgIteratorFactory; |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
14 | use alg_tools::euclidean::Euclidean; |
32 | 15 | use alg_tools::sets::Cube; |
16 | use alg_tools::loc::Loc; | |
35 | 17 | use alg_tools::mapping::{Mapping, DifferentiableMapping, Instance}; |
18 | use alg_tools::norms::Norm; | |
32 | 19 | use alg_tools::bisection_tree::{ |
20 | BTFN, | |
21 | PreBTFN, | |
22 | Bounds, | |
23 | BTNodeLookup, | |
24 | BTNode, | |
25 | BTSearch, | |
26 | P2Minimise, | |
27 | SupportGenerator, | |
28 | LocalAnalysis, | |
29 | //Bounded, | |
30 | }; | |
31 | use alg_tools::mapping::RealMapping; | |
32 | use alg_tools::nalgebra_support::ToNalgebraRealField; | |
35 | 33 | use alg_tools::norms::{L2, Linfinity}; |
32 | 34 | |
35 | use crate::types::*; | |
35 | 36 | use crate::measures::{DiscreteMeasure, Radon, RNDM}; |
32 | 37 | use crate::measures::merging::{ |
35 | 38 | SpikeMergingMethod, |
32 | 39 | SpikeMerging, |
40 | }; | |
35 | 41 | use crate::forward_model::{ |
42 | ForwardModel, | |
43 | AdjointProductBoundedBy, | |
44 | LipschitzValues, | |
45 | }; | |
32 | 46 | use crate::seminorms::DiscreteMeasureOp; |
47 | //use crate::tolerance::Tolerance; | |
48 | use crate::plot::{ | |
49 | SeqPlotter, | |
50 | Plotting, | |
51 | PlotLookup | |
52 | }; | |
53 | use crate::fb::*; | |
54 | use crate::regularisation::SlidingRegTerm; | |
55 | use crate::dataterm::{ | |
56 | L2Squared, | |
57 | //DataTerm, | |
58 | calculate_residual, | |
59 | calculate_residual2, | |
60 | }; | |
35 | 61 | //use crate::transport::TransportLipschitz; |
62 | ||
63 | /// Transport settings for [`pointsource_sliding_fb_reg`]. | |
64 | #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] | |
65 | #[serde(default)] | |
66 | pub struct TransportConfig<F : Float> { | |
67 | /// Transport step length $θ$ normalised to $(0, 1)$. | |
68 | pub θ0 : F, | |
69 | /// Factor in $(0, 1)$ for decreasing transport to adapt to tolerance. | |
70 | pub adaptation : F, | |
71 | /// Transport tolerance wrt. ω | |
72 | pub tolerance_ω : F, | |
73 | /// Transport tolerance wrt. ∇v | |
74 | pub tolerance_dv : F, | |
75 | } | |
76 | ||
77 | #[replace_float_literals(F::cast_from(literal))] | |
78 | impl <F : Float> TransportConfig<F> { | |
79 | /// Check that the parameters are ok. Panics if not. | |
80 | pub fn check(&self) { | |
81 | assert!(self.θ0 > 0.0); | |
82 | assert!(0.0 < self.adaptation && self.adaptation < 1.0); | |
83 | assert!(self.tolerance_dv > 0.0); | |
84 | assert!(self.tolerance_ω > 0.0); | |
85 | } | |
86 | } | |
87 | ||
88 | #[replace_float_literals(F::cast_from(literal))] | |
89 | impl<F : Float> Default for TransportConfig<F> { | |
90 | fn default() -> Self { | |
91 | TransportConfig { | |
92 | θ0 : 0.01, | |
93 | adaptation : 0.9, | |
94 | tolerance_ω : 1000.0, // TODO: no idea what this should be | |
95 | tolerance_dv : 1000.0, // TODO: no idea what this should be | |
96 | } | |
97 | } | |
98 | } | |
32 | 99 | |
100 | /// Settings for [`pointsource_sliding_fb_reg`]. | |
101 | #[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] | |
102 | #[serde(default)] | |
103 | pub struct SlidingFBConfig<F : Float> { | |
104 | /// Step length scaling | |
105 | pub τ0 : F, | |
35 | 106 | /// Transport parameters |
107 | pub transport : TransportConfig<F>, | |
32 | 108 | /// Generic parameters |
109 | pub insertion : FBGenericConfig<F>, | |
110 | } | |
111 | ||
112 | #[replace_float_literals(F::cast_from(literal))] | |
113 | impl<F : Float> Default for SlidingFBConfig<F> { | |
114 | fn default() -> Self { | |
115 | SlidingFBConfig { | |
116 | τ0 : 0.99, | |
35 | 117 | transport : Default::default(), |
32 | 118 | insertion : Default::default() |
119 | } | |
120 | } | |
121 | } | |
122 | ||
35 | 123 | /// Internal type of adaptive transport step length calculation |
124 | pub(crate) enum TransportStepLength<F : Float, G : Fn(F, F) -> F> { | |
125 | /// Fixed, known step length | |
126 | Fixed(F), | |
127 | /// Adaptive step length, only wrt. maximum transport. | |
128 | /// Content of `l` depends on use case, while `g` calculates the step length from `l`. | |
129 | AdaptiveMax{ l : F, max_transport : F, g : G }, | |
130 | /// Adaptive step length. | |
131 | /// Content of `l` depends on use case, while `g` calculates the step length from `l`. | |
132 | FullyAdaptive{ l : F, max_transport : F, g : G }, | |
133 | } | |
134 | ||
135 | /// Constrution and a priori transport adaptation. | |
32 | 136 | #[replace_float_literals(F::cast_from(literal))] |
35 | 137 | pub(crate) fn initial_transport<F, G, D, Observable, const N : usize>( |
138 | γ1 : &mut RNDM<F, N>, | |
139 | μ : &mut RNDM<F, N>, | |
140 | opAapply : impl Fn(&RNDM<F, N>) -> Observable, | |
141 | ε : F, | |
142 | τ : F, | |
143 | θ_or_adaptive : &mut TransportStepLength<F, G>, | |
144 | opAnorm : F, | |
145 | v : D, | |
146 | tconfig : &TransportConfig<F> | |
147 | ) -> (Vec<F>, RNDM<F, N>) | |
148 | where | |
149 | F : Float + ToNalgebraRealField, | |
150 | G : Fn(F, F) -> F, | |
151 | Observable : Euclidean<F, Output=Observable>, | |
152 | for<'a> &'a Observable : Instance<Observable>, | |
153 | //for<'b> A::Preadjoint<'b> : LipschitzValues<FloatType=F>, | |
154 | D : DifferentiableMapping<Loc<F, N>, DerivativeDomain=Loc<F, N>>, | |
155 | { | |
156 | ||
157 | use TransportStepLength::*; | |
158 | ||
159 | // Save current base point and shift μ to new positions. Idea is that | |
160 | // μ_base(_masses) = μ^k (vector of masses) | |
161 | // μ_base_minus_γ0 = μ^k - π_♯^0γ^{k+1} | |
162 | // γ1 = π_♯^1γ^{k+1} | |
163 | // μ = μ^{k+1} | |
164 | let μ_base_masses : Vec<F> = μ.iter_masses().collect(); | |
165 | let mut μ_base_minus_γ0 = μ.clone(); // Weights will be set in the loop below. | |
166 | // Construct μ^{k+1} and π_♯^1γ^{k+1} initial candidates | |
167 | //let mut sum_norm_dv = 0.0; | |
168 | let γ_prev_len = γ1.len(); | |
169 | assert!(μ.len() >= γ_prev_len); | |
170 | γ1.extend(μ[γ_prev_len..].iter().cloned()); | |
171 | ||
172 | // Calculate initial transport and step length. | |
173 | // First calculate initial transported weights | |
174 | for (δ, ρ) in izip!(μ.iter_spikes(), γ1.iter_spikes_mut()) { | |
175 | // If old transport has opposing sign, the new transport will be none. | |
176 | ρ.α = if (ρ.α > 0.0 && δ.α < 0.0) || (ρ.α < 0.0 && δ.α > 0.0) { | |
177 | 0.0 | |
178 | } else { | |
179 | δ.α | |
180 | }; | |
181 | }; | |
182 | ||
183 | // A priori transport adaptation based on bounding 2 ‖A‖ ‖A(γ₁-γ₀)‖‖γ‖ by scaling γ. | |
184 | // 1. Calculate transport rays. | |
185 | // If the Lipschitz factor of the values v=∇F(μ) are not known, estimate it. | |
186 | match *θ_or_adaptive { | |
187 | Fixed(θ) => { | |
188 | let θτ = τ * θ; | |
189 | for (δ, ρ) in izip!(μ.iter_spikes(), γ1.iter_spikes_mut()) { | |
190 | ρ.x = δ.x - v.differential(&δ.x) * (ρ.α.signum() * θτ); | |
191 | } | |
192 | }, | |
193 | AdaptiveMax{ l : ℓ_v, ref mut max_transport, g : ref calculate_θ } => { | |
194 | *max_transport = max_transport.max(γ1.norm(Radon)); | |
195 | let θτ = τ * calculate_θ(ℓ_v, *max_transport); | |
196 | for (δ, ρ) in izip!(μ.iter_spikes(), γ1.iter_spikes_mut()) { | |
197 | ρ.x = δ.x - v.differential(&δ.x) * (ρ.α.signum() * θτ); | |
198 | } | |
199 | }, | |
200 | FullyAdaptive{ l : ref mut adaptive_ℓ_v, ref mut max_transport, g : ref calculate_θ } => { | |
201 | *max_transport = max_transport.max(γ1.norm(Radon)); | |
202 | let mut θ = calculate_θ(*adaptive_ℓ_v, *max_transport); | |
203 | loop { | |
204 | let θτ = τ * θ; | |
205 | for (δ, ρ) in izip!(μ.iter_spikes(), γ1.iter_spikes_mut()) { | |
206 | let dv_x = v.differential(&δ.x); | |
207 | ρ.x = δ.x - &dv_x * (ρ.α.signum() * θτ); | |
208 | // Estimate Lipschitz factor of ∇v | |
209 | let this_ℓ_v = (dv_x - v.differential(&ρ.x)).norm2(); | |
210 | *adaptive_ℓ_v = adaptive_ℓ_v.max(this_ℓ_v); | |
211 | } | |
212 | let new_θ = calculate_θ(*adaptive_ℓ_v / tconfig.adaptation, *max_transport); | |
213 | if new_θ <= θ { | |
214 | break | |
215 | } | |
216 | θ = new_θ; | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
217 | } |
32 | 218 | } |
35 | 219 | } |
220 | ||
221 | // 2. Adjust transport mass, if needed. | |
222 | // This tries to remove the smallest transport masses first. | |
223 | if true { | |
224 | // Alternative 1 : subtract same amount from all transport rays until reaching zero | |
225 | loop { | |
226 | let nr =γ1.norm(Radon); | |
227 | let n = τ * 2.0 * opAnorm * (opAapply(&*γ1)-opAapply(&*μ)).norm2(); | |
228 | if n <= 0.0 || nr <= 0.0 { | |
229 | break | |
230 | } | |
231 | let reduction_needed = nr - (ε * tconfig.tolerance_dv / n); | |
232 | if reduction_needed <= 0.0 { | |
233 | break | |
234 | } | |
235 | let (min_nonzero, n_nonzero) = γ1.iter_masses() | |
236 | .map(|α| α.abs()) | |
237 | .filter(|α| *α > F::EPSILON) | |
238 | .fold((F::INFINITY, 0), |(a, n), b| (a.min(b), n+1)); | |
239 | assert!(n_nonzero > 0); | |
240 | // Reduction that can be done in all nonzero spikes simultaneously | |
241 | let h = (reduction_needed / F::cast_from(n_nonzero)).min(min_nonzero); | |
242 | for (δ, ρ) in izip!(μ.iter_spikes_mut(), γ1.iter_spikes_mut()) { | |
243 | ρ.α = ρ.α.signum() * (ρ.α.abs() - h).max(0.0); | |
244 | δ.α = ρ.α; | |
245 | } | |
246 | if min_nonzero * F::cast_from(n_nonzero) >= reduction_needed { | |
247 | break | |
248 | } | |
249 | } | |
250 | } else { | |
251 | // Alternative 2: first reduce transport rays with greater effect based on differential. | |
252 | // This is a an inefficient quick-and-dirty implementation. | |
253 | loop { | |
254 | let nr = γ1.norm(Radon); | |
255 | let a = opAapply(&*γ1)-opAapply(&*μ); | |
256 | let na = a.norm2(); | |
257 | let n = τ * 2.0 * opAnorm * na; | |
258 | if n <= 0.0 || nr <= 0.0 { | |
259 | break | |
260 | } | |
261 | let reduction_needed = nr - (ε * tconfig.tolerance_dv / n); | |
262 | if reduction_needed <= 0.0 { | |
263 | break | |
264 | } | |
265 | let mut max_d = 0.0; | |
266 | let mut max_d_ind = 0; | |
267 | for (δ, ρ, i) in izip!(μ.iter_spikes_mut(), γ1.iter_spikes(), 0..) { | |
268 | // Calculate differential of ‖A(γ₁-γ₀)‖‖γ‖ wrt. each spike | |
269 | let s = δ.α.signum(); | |
270 | // TODO: this is very inefficient implementation due to the limitations | |
271 | // of the closure parameters. | |
272 | let δ1 = DiscreteMeasure::from([(ρ.x, s)]); | |
273 | let δ2 = DiscreteMeasure::from([(δ.x, s)]); | |
274 | let a_part = opAapply(&δ1)-opAapply(&δ2); | |
275 | let d = a.dot(&a_part)/na * nr + 2.0 * na; | |
276 | if d > max_d { | |
277 | max_d = d; | |
278 | max_d_ind = i; | |
279 | } | |
280 | } | |
281 | // Just set mass to zero for transport ray with greater differential | |
282 | assert!(max_d > 0.0); | |
283 | γ1[max_d_ind].α = 0.0; | |
284 | μ[max_d_ind].α = 0.0; | |
285 | } | |
286 | } | |
287 | ||
288 | // Set initial guess for μ=μ^{k+1}. | |
289 | for (δ, ρ, &β) in izip!(μ.iter_spikes_mut(), γ1.iter_spikes(), μ_base_masses.iter()) { | |
290 | if ρ.α.abs() > F::EPSILON { | |
291 | δ.x = ρ.x; | |
292 | //δ.α = ρ.α; // already set above | |
293 | } else { | |
294 | δ.α = β; | |
295 | } | |
296 | } | |
297 | // Calculate μ^k-π_♯^0γ^{k+1} and v̆ = A_*(A[μ_transported + μ_transported_base]-b) | |
298 | μ_base_minus_γ0.set_masses(μ_base_masses.iter().zip(γ1.iter_masses()) | |
299 | .map(|(&a,b)| a - b)); | |
300 | (μ_base_masses, μ_base_minus_γ0) | |
301 | } | |
302 | ||
303 | /// A posteriori transport adaptation. | |
304 | #[replace_float_literals(F::cast_from(literal))] | |
305 | pub(crate) fn aposteriori_transport<F, const N : usize>( | |
306 | γ1 : &mut RNDM<F, N>, | |
307 | μ : &mut RNDM<F, N>, | |
308 | μ_base_minus_γ0 : &mut RNDM<F, N>, | |
309 | μ_base_masses : &Vec<F>, | |
310 | ε : F, | |
311 | tconfig : &TransportConfig<F> | |
312 | ) -> bool | |
313 | where F : Float + ToNalgebraRealField { | |
314 | ||
315 | // 1. If π_♯^1γ^{k+1} = γ1 has non-zero mass at some point y, but μ = μ^{k+1} does not, | |
316 | // then the ansatz ∇w̃_x(y) = w^{k+1}(y) may not be satisfied. So set the mass of γ1 | |
317 | // at that point to zero, and retry. | |
318 | let mut all_ok = true; | |
319 | for (α_μ, α_γ1) in izip!(μ.iter_masses(), γ1.iter_masses_mut()) { | |
320 | if α_μ == 0.0 && *α_γ1 != 0.0 { | |
321 | all_ok = false; | |
322 | *α_γ1 = 0.0; | |
323 | } | |
324 | } | |
325 | ||
326 | // 2. Through bounding ∫ B_ω(y, z) dλ(x, y, z). | |
327 | // through the estimate ≤ C ‖Δ‖‖γ^{k+1}‖ for Δ := μ^{k+1}-μ^k-(π_♯^1-π_♯^0)γ^{k+1}, | |
328 | // which holds for some some C if the convolution kernel in 𝒟 has Lipschitz gradient. | |
329 | let nγ = γ1.norm(Radon); | |
330 | let nΔ = μ_base_minus_γ0.norm(Radon) + μ.dist_matching(&γ1); | |
331 | let t = ε * tconfig.tolerance_ω; | |
332 | if nγ*nΔ > t { | |
333 | // Since t/(nγ*nΔ)<1, and the constant tconfig.adaptation < 1, | |
334 | // this will guarantee that eventually ‖γ‖ decreases sufficiently that we | |
335 | // will not enter here. | |
336 | *γ1 *= tconfig.adaptation * t / ( nγ * nΔ ); | |
337 | all_ok = false | |
338 | } | |
339 | ||
340 | if !all_ok { | |
341 | // Update weights for μ_base_minus_γ0 = μ^k - π_♯^0γ^{k+1} | |
342 | μ_base_minus_γ0.set_masses(μ_base_masses.iter().zip(γ1.iter_masses()) | |
343 | .map(|(&a,b)| a - b)); | |
344 | ||
345 | } | |
346 | ||
347 | all_ok | |
32 | 348 | } |
349 | ||
350 | /// Iteratively solve the pointsource localisation problem using sliding forward-backward | |
351 | /// splitting | |
352 | /// | |
35 | 353 | /// The parametrisation is as for [`pointsource_fb_reg`]. |
32 | 354 | /// Inertia is currently not supported. |
355 | #[replace_float_literals(F::cast_from(literal))] | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
356 | pub fn pointsource_sliding_fb_reg<'a, F, I, A, GA, 𝒟, BTA, BT𝒟, G𝒟, S, K, Reg, const N : usize>( |
32 | 357 | opA : &'a A, |
358 | b : &A::Observable, | |
359 | reg : Reg, | |
360 | op𝒟 : &'a 𝒟, | |
35 | 361 | config : &SlidingFBConfig<F>, |
32 | 362 | iterator : I, |
363 | mut plotter : SeqPlotter<F, N>, | |
35 | 364 | ) -> RNDM<F, N> |
32 | 365 | where F : Float + ToNalgebraRealField, |
366 | I : AlgIteratorFactory<IterInfo<F, N>>, | |
35 | 367 | for<'b> &'b A::Observable : std::ops::Neg<Output=A::Observable> + Instance<A::Observable>, |
368 | for<'b> A::Preadjoint<'b> : LipschitzValues<FloatType=F>, | |
369 | A::PreadjointCodomain : DifferentiableMapping< | |
370 | Loc<F, N>, DerivativeDomain=Loc<F, N>, Codomain=F | |
371 | >, | |
32 | 372 | GA : SupportGenerator<F, N, SupportType = S, Id = usize> + Clone, |
35 | 373 | A : ForwardModel<RNDM<F, N>, F, PreadjointCodomain = BTFN<F, GA, BTA, N>> |
374 | + AdjointProductBoundedBy<RNDM<F, N>, 𝒟, FloatType=F>, | |
375 | //+ TransportLipschitz<L2Squared, FloatType=F>, | |
32 | 376 | BTA : BTSearch<F, N, Data=usize, Agg=Bounds<F>>, |
377 | G𝒟 : SupportGenerator<F, N, SupportType = K, Id = usize> + Clone, | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
378 | 𝒟 : DiscreteMeasureOp<Loc<F, N>, F, PreCodomain = PreBTFN<F, G𝒟, N>, |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
379 | Codomain = BTFN<F, G𝒟, BT𝒟, N>>, |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
380 | BT𝒟 : BTSearch<F, N, Data=usize, Agg=Bounds<F>>, |
32 | 381 | S: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N> |
35 | 382 | + DifferentiableMapping<Loc<F, N>, DerivativeDomain=Loc<F,N>>, |
32 | 383 | K: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, |
35 | 384 | //+ Differentiable<Loc<F, N>, Derivative=Loc<F,N>>, |
32 | 385 | BTNodeLookup: BTNode<F, usize, Bounds<F>, N>, |
386 | Cube<F, N>: P2Minimise<Loc<F, N>, F>, | |
387 | PlotLookup : Plotting<N>, | |
35 | 388 | RNDM<F, N> : SpikeMerging<F>, |
32 | 389 | Reg : SlidingRegTerm<F, N> { |
390 | ||
35 | 391 | // Check parameters |
392 | assert!(config.τ0 > 0.0, "Invalid step length parameter"); | |
393 | config.transport.check(); | |
32 | 394 | |
395 | // Initialise iterates | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
396 | let mut μ = DiscreteMeasure::new(); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
397 | let mut γ1 = DiscreteMeasure::new(); |
35 | 398 | let mut residual = -b; // Has to equal $Aμ-b$. |
399 | ||
400 | // Set up parameters | |
401 | let op𝒟norm = op𝒟.opnorm_bound(Radon, Linfinity); | |
402 | let opAnorm = opA.opnorm_bound(Radon, L2); | |
403 | //let max_transport = config.max_transport.scale | |
404 | // * reg.radon_norm_bound(b.norm2_squared() / 2.0); | |
405 | //let ℓ = opA.transport.lipschitz_factor(L2Squared) * max_transport; | |
406 | let ℓ = 0.0; | |
407 | let τ = config.τ0 / opA.adjoint_product_bound(&op𝒟).unwrap(); | |
408 | let calculate_θ = |ℓ_v, _| config.transport.θ0 / (τ*(ℓ + ℓ_v)); | |
409 | let mut θ_or_adaptive = match opA.preadjoint().value_diff_unit_lipschitz_factor() { | |
410 | // We only estimate w (the uniform Lipschitz for of v), if we also estimate ℓ_v | |
411 | // (the uniform Lipschitz factor of ∇v). | |
412 | // We assume that the residual is decreasing. | |
413 | Some(ℓ_v0) => TransportStepLength::Fixed(calculate_θ(ℓ_v0 * residual.norm2(), 0.0)), | |
414 | None => TransportStepLength::FullyAdaptive { | |
415 | l : 0.0, | |
416 | max_transport : 0.0, | |
417 | g : calculate_θ | |
418 | }, | |
419 | }; | |
420 | // We multiply tolerance by τ for FB since our subproblems depending on tolerances are scaled | |
421 | // by τ compared to the conditional gradient approach. | |
422 | let tolerance = config.insertion.tolerance * τ * reg.tolerance_scaling(); | |
423 | let mut ε = tolerance.initial(); | |
424 | ||
425 | // Statistics | |
426 | let full_stats = |residual : &A::Observable, | |
427 | μ : &RNDM<F, N>, | |
428 | ε, stats| IterInfo { | |
429 | value : residual.norm2_squared_div2() + reg.apply(μ), | |
430 | n_spikes : μ.len(), | |
431 | ε, | |
432 | // postprocessing: config.insertion.postprocessing.then(|| μ.clone()), | |
433 | .. stats | |
434 | }; | |
32 | 435 | let mut stats = IterInfo::new(); |
436 | ||
437 | // Run the algorithm | |
35 | 438 | for state in iterator.iter_init(|| full_stats(&residual, &μ, ε, stats.clone())) { |
439 | // Calculate initial transport | |
440 | let v = opA.preadjoint().apply(residual); | |
441 | let (μ_base_masses, mut μ_base_minus_γ0) = initial_transport( | |
442 | &mut γ1, &mut μ, |ν| opA.apply(ν), | |
443 | ε, τ, &mut θ_or_adaptive, opAnorm, | |
444 | v, &config.transport, | |
445 | ); | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
446 | |
32 | 447 | // Solve finite-dimensional subproblem several times until the dual variable for the |
448 | // regularisation term conforms to the assumptions made for the transport above. | |
35 | 449 | let (d, _within_tolerances, τv̆) = 'adapt_transport: loop { |
450 | // Calculate τv̆ = τA_*(A[μ_transported + μ_transported_base]-b) | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
451 | let residual_μ̆ = calculate_residual2(&γ1, &μ_base_minus_γ0, opA, b); |
35 | 452 | let τv̆ = opA.preadjoint().apply(residual_μ̆ * τ); |
32 | 453 | |
454 | // Construct μ^{k+1} by solving finite-dimensional subproblems and insert new spikes. | |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
455 | let (d, within_tolerances) = insert_and_reweigh( |
35 | 456 | &mut μ, &τv̆, &γ1, Some(&μ_base_minus_γ0), |
32 | 457 | op𝒟, op𝒟norm, |
35 | 458 | τ, ε, &config.insertion, |
459 | ®, &state, &mut stats, | |
32 | 460 | ); |
461 | ||
35 | 462 | // A posteriori transport adaptation. |
463 | if aposteriori_transport( | |
464 | &mut γ1, &mut μ, &mut μ_base_minus_γ0, &μ_base_masses, | |
465 | ε, &config.transport | |
466 | ) { | |
467 | break 'adapt_transport (d, within_tolerances, τv̆) | |
32 | 468 | } |
469 | }; | |
470 | ||
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
471 | stats.untransported_fraction = Some({ |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
472 | assert_eq!(μ_base_masses.len(), γ1.len()); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
473 | let (a, b) = stats.untransported_fraction.unwrap_or((0.0, 0.0)); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
474 | let source = μ_base_masses.iter().map(|v| v.abs()).sum(); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
475 | (a + μ_base_minus_γ0.norm(Radon), b + source) |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
476 | }); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
477 | stats.transport_error = Some({ |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
478 | assert_eq!(μ_base_masses.len(), γ1.len()); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
479 | let (a, b) = stats.transport_error.unwrap_or((0.0, 0.0)); |
35 | 480 | (a + μ.dist_matching(&γ1), b + γ1.norm(Radon)) |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
481 | }); |
32 | 482 | |
35 | 483 | // Merge spikes. |
484 | // This expects the prune below to prune γ. | |
485 | // TODO: This may not work correctly in all cases. | |
486 | let ins = &config.insertion; | |
487 | if ins.merge_now(&state) { | |
488 | if let SpikeMergingMethod::None = ins.merging { | |
489 | } else { | |
490 | stats.merged += μ.merge_spikes(ins.merging, |μ_candidate| { | |
491 | let ν = μ_candidate.sub_matching(&γ1)-&μ_base_minus_γ0; | |
492 | let mut d = &τv̆ + op𝒟.preapply(ν); | |
493 | reg.verify_merge_candidate(&mut d, μ_candidate, τ, ε, ins) | |
494 | }); | |
495 | } | |
496 | } | |
497 | ||
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
498 | // Prune spikes with zero weight. To maintain correct ordering between μ and γ1, also the |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
499 | // latter needs to be pruned when μ is. |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
500 | // TODO: This could do with a two-vector Vec::retain to avoid copies. |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
501 | let μ_new = DiscreteMeasure::from_iter(μ.iter_spikes().filter(|δ| δ.α != F::ZERO).cloned()); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
502 | if μ_new.len() != μ.len() { |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
503 | let mut μ_iter = μ.iter_spikes(); |
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
504 | γ1.prune_by(|_| μ_iter.next().unwrap().α != F::ZERO); |
35 | 505 | stats.pruned += μ.len() - μ_new.len(); |
34
efa60bc4f743
Radon FB + sliding improvements
Tuomo Valkonen <tuomov@iki.fi>
parents:
32
diff
changeset
|
506 | μ = μ_new; |
32 | 507 | } |
508 | ||
509 | // Update residual | |
510 | residual = calculate_residual(&μ, opA, b); | |
511 | ||
35 | 512 | let iter = state.iteration(); |
32 | 513 | stats.this_iters += 1; |
514 | ||
35 | 515 | // Give statistics if requested |
32 | 516 | state.if_verbose(|| { |
35 | 517 | plotter.plot_spikes(iter, Some(&d), Some(&τv̆), &μ); |
518 | full_stats(&residual, &μ, ε, std::mem::replace(&mut stats, IterInfo::new())) | |
519 | }); | |
32 | 520 | |
35 | 521 | // Update main tolerance for next iteration |
522 | ε = tolerance.update(ε, iter); | |
523 | } | |
524 | ||
525 | postprocess(μ, &config.insertion, L2Squared, opA, b) | |
32 | 526 | } |