| |
1 /*! |
| |
2 Solver for the point source localisation problem using a simplified forward-backward splitting method. |
| |
3 |
| |
4 Instead of the $𝒟$-norm of `fb.rs`, this uses a standard Radon norm for the proximal map. |
| |
5 */ |
| |
6 |
| |
7 use numeric_literals::replace_float_literals; |
| |
8 use serde::{Serialize, Deserialize}; |
| |
9 use nalgebra::DVector; |
| |
10 |
| |
11 use alg_tools::iterate::{ |
| |
12 AlgIteratorIteration, |
| |
13 AlgIterator |
| |
14 }; |
| |
15 use alg_tools::norms::{L2, Norm}; |
| |
16 use alg_tools::linops::Mapping; |
| |
17 use alg_tools::bisection_tree::{ |
| |
18 BTFN, |
| |
19 Bounds, |
| |
20 BTSearch, |
| |
21 SupportGenerator, |
| |
22 LocalAnalysis, |
| |
23 }; |
| |
24 use alg_tools::mapping::RealMapping; |
| |
25 use alg_tools::nalgebra_support::ToNalgebraRealField; |
| |
26 |
| |
27 use crate::types::*; |
| |
28 use crate::measures::{ |
| |
29 RNDM, |
| |
30 DeltaMeasure, |
| |
31 Radon, |
| |
32 }; |
| |
33 use crate::measures::merging::SpikeMerging; |
| |
34 use crate::regularisation::RegTerm; |
| |
35 use crate::forward_model::{ |
| |
36 ForwardModel, |
| |
37 AdjointProductBoundedBy |
| |
38 }; |
| |
39 use super::{ |
| |
40 FBGenericConfig, |
| |
41 ProxPenalty, |
| |
42 }; |
| |
43 |
| |
44 /// Radon-norm squared proximal penalty |
| |
45 |
| |
46 #[derive(Copy,Clone,Serialize,Deserialize)] |
| |
47 pub struct RadonSquared; |
| |
48 |
| |
49 #[replace_float_literals(F::cast_from(literal))] |
| |
50 impl<F, GA, BTA, S, Reg, const N : usize> |
| |
51 ProxPenalty<F, BTFN<F, GA, BTA, N>, Reg, N> for RadonSquared |
| |
52 where |
| |
53 F : Float + ToNalgebraRealField, |
| |
54 GA : SupportGenerator<F, N, SupportType = S, Id = usize> + Clone, |
| |
55 BTA : BTSearch<F, N, Data=usize, Agg=Bounds<F>>, |
| |
56 S: RealMapping<F, N> + LocalAnalysis<F, Bounds<F>, N>, |
| |
57 Reg : RegTerm<F, N>, |
| |
58 RNDM<F, N> : SpikeMerging<F>, |
| |
59 { |
| |
60 type ReturnMapping = BTFN<F, GA, BTA, N>; |
| |
61 |
| |
62 fn insert_and_reweigh<I>( |
| |
63 &self, |
| |
64 μ : &mut RNDM<F, N>, |
| |
65 τv : &mut BTFN<F, GA, BTA, N>, |
| |
66 μ_base : &RNDM<F, N>, |
| |
67 ν_delta: Option<&RNDM<F, N>>, |
| |
68 τ : F, |
| |
69 ε : F, |
| |
70 config : &FBGenericConfig<F>, |
| |
71 reg : &Reg, |
| |
72 _state : &AlgIteratorIteration<I>, |
| |
73 stats : &mut IterInfo<F, N>, |
| |
74 ) -> (Option<Self::ReturnMapping>, bool) |
| |
75 where |
| |
76 I : AlgIterator |
| |
77 { |
| |
78 let mut y = μ_base.masses_dvector(); |
| |
79 |
| |
80 assert!(μ_base.len() <= μ.len()); |
| |
81 |
| |
82 'i_and_w: for i in 0..=1 { |
| |
83 // Optimise weights |
| |
84 if μ.len() > 0 { |
| |
85 // Form finite-dimensional subproblem. The subproblem references to the original μ^k |
| |
86 // from the beginning of the iteration are all contained in the immutable c and g. |
| |
87 // TODO: observe negation of -τv after switch from minus_τv: finite-dimensional |
| |
88 // problems have not yet been updated to sign change. |
| |
89 let g̃ = DVector::from_iterator(μ.len(), |
| |
90 μ.iter_locations() |
| |
91 .map(|ζ| - F::to_nalgebra_mixed(τv.apply(ζ)))); |
| |
92 let mut x = μ.masses_dvector(); |
| |
93 y.extend(std::iter::repeat(0.0.to_nalgebra_mixed()).take(0.max(x.len()-y.len()))); |
| |
94 assert_eq!(y.len(), x.len()); |
| |
95 // Solve finite-dimensional subproblem. |
| |
96 // TODO: This assumes that ν_delta has no common locations with μ-μ_base, to |
| |
97 // ignore it. |
| |
98 stats.inner_iters += reg.solve_findim_l1squared(&y, &g̃, τ, &mut x, ε, config); |
| |
99 |
| |
100 // Update masses of μ based on solution of finite-dimensional subproblem. |
| |
101 μ.set_masses_dvector(&x); |
| |
102 } |
| |
103 |
| |
104 if i>0 { |
| |
105 // Simple debugging test to see if more inserts would be needed. Doesn't seem so. |
| |
106 //let n = μ.dist_matching(μ_base); |
| |
107 //println!("{:?}", reg.find_tolerance_violation_slack(τv, τ, ε, false, config, n)); |
| |
108 break 'i_and_w |
| |
109 } |
| |
110 |
| |
111 // Calculate ‖μ - μ_base‖_ℳ |
| |
112 // TODO: This assumes that ν_delta has no common locations with μ-μ_base. |
| |
113 let n = μ.dist_matching(μ_base) + ν_delta.map_or(0.0, |ν| ν.norm(Radon)); |
| |
114 |
| |
115 // Find a spike to insert, if needed. |
| |
116 // This only check the overall tolerances, not tolerances on support of μ-μ_base or μ, |
| |
117 // which are supposed to have been guaranteed by the finite-dimensional weight optimisation. |
| |
118 match reg.find_tolerance_violation_slack(τv, τ, ε, false, config, n) { |
| |
119 None => { break 'i_and_w }, |
| |
120 Some((ξ, _v_ξ, _in_bounds)) => { |
| |
121 // Weight is found out by running the finite-dimensional optimisation algorithm |
| |
122 // above |
| |
123 *μ += DeltaMeasure { x : ξ, α : 0.0 }; |
| |
124 stats.inserted += 1; |
| |
125 } |
| |
126 }; |
| |
127 } |
| |
128 |
| |
129 (None, true) |
| |
130 } |
| |
131 |
| |
132 fn merge_spikes( |
| |
133 &self, |
| |
134 μ : &mut RNDM<F, N>, |
| |
135 τv : &mut BTFN<F, GA, BTA, N>, |
| |
136 μ_base : &RNDM<F, N>, |
| |
137 ν_delta: Option<&RNDM<F, N>>, |
| |
138 τ : F, |
| |
139 ε : F, |
| |
140 config : &FBGenericConfig<F>, |
| |
141 reg : &Reg, |
| |
142 fitness : Option<impl Fn(&RNDM<F, N>) -> F>, |
| |
143 ) -> usize |
| |
144 { |
| |
145 if config.fitness_merging { |
| |
146 if let Some(f) = fitness { |
| |
147 return μ.merge_spikes_fitness(config.merging, f, |&v| v) |
| |
148 .1 |
| |
149 } |
| |
150 } |
| |
151 μ.merge_spikes(config.merging, |μ_candidate| { |
| |
152 // Important: μ_candidate's new points are afterwards, |
| |
153 // and do not conflict with μ_base. |
| |
154 // TODO: could simplify to requiring μ_base instead of μ_radon. |
| |
155 // but may complicate with sliding base's exgtra points that need to be |
| |
156 // after μ_candidate's extra points. |
| |
157 // TODO: doesn't seem to work, maybe need to merge μ_base as well? |
| |
158 // Although that doesn't seem to make sense. |
| |
159 let μ_radon = match ν_delta { |
| |
160 None => μ_candidate.sub_matching(μ_base), |
| |
161 Some(ν) => μ_candidate.sub_matching(μ_base) - ν, |
| |
162 }; |
| |
163 reg.verify_merge_candidate_radonsq(τv, μ_candidate, τ, ε, &config, &μ_radon) |
| |
164 //let n = μ_candidate.dist_matching(μ_base); |
| |
165 //reg.find_tolerance_violation_slack(τv, τ, ε, false, config, n).is_none() |
| |
166 }) |
| |
167 } |
| |
168 } |
| |
169 |
| |
170 |
| |
171 impl<F, A, const N : usize> AdjointProductBoundedBy<RNDM<F, N>, RadonSquared> |
| |
172 for A |
| |
173 where |
| |
174 F : Float, |
| |
175 A : ForwardModel<RNDM<F, N>, F> |
| |
176 { |
| |
177 type FloatType = F; |
| |
178 |
| |
179 fn adjoint_product_bound(&self, _ : &RadonSquared) -> Option<Self::FloatType> { |
| |
180 self.opnorm_bound(Radon, L2).powi(2).into() |
| |
181 } |
| |
182 } |