src/subproblem/l1squared_nonneg.rs

changeset 72
e9a460a0e638
parent 63
7a8a55fd41c0
equal deleted inserted replaced
71:e2953ffd4e0b 72:e9a460a0e638
1 /*! 1 /*!
2 Iterative algorithms for solving the finite-dimensional subproblem with constraint. 2 Iterative algorithms for solving the finite-dimensional subproblem with constraint.
3 */ 3 */
4 4
5 use itertools::izip; 5 use itertools::izip;
6 use nalgebra::DVector; 6 use nalgebra::{constraint::ShapeConstraint, DVector, Dyn, Storage, StorageMut, Vector, U1};
7 use numeric_literals::replace_float_literals; 7 use numeric_literals::replace_float_literals;
8 //use std::iter::zip;
9 use std::cmp::Ordering::*;
10 8
11 use alg_tools::iterate::{AlgIteratorFactory, AlgIteratorState}; 9 use alg_tools::iterate::{AlgIteratorFactory, AlgIteratorState};
12 use alg_tools::nalgebra_support::ToNalgebraRealField; 10 use alg_tools::nalgebra_support::{StridesOk, ToNalgebraRealField};
13 use alg_tools::norms::{Dist, L1}; 11 use alg_tools::norms::{Dist, L1};
14 12
15 use super::l1squared_unconstrained::l1squared_prox; 13 use super::l1squared_unconstrained::l1squared_prox;
16 use super::nonneg::nonneg_soft_thresholding; 14 use super::nonneg::nonneg_soft_thresholding;
17 use super::{InnerMethod, InnerSettings}; 15 use super::{InnerMethod, InnerSettings};
18 use crate::types::*; 16 use crate::types::*;
19 17
20 /// Return maximum of `dist` and distnce of inteval `[lb, ub]` to zero. 18 /// Return maximum of `dist` and distnce of inteval `[lb, ub]` to zero.
21 #[replace_float_literals(F::cast_from(literal))] 19 #[replace_float_literals(F::cast_from(literal))]
20 #[inline]
22 pub(super) fn max_interval_dist_to_zero<F: Float>(dist: F, lb: F, ub: F) -> F { 21 pub(super) fn max_interval_dist_to_zero<F: Float>(dist: F, lb: F, ub: F) -> F {
23 if lb < 0.0 { 22 if lb < 0.0 {
24 if ub > 0.0 { 23 if ub > 0.0 {
25 dist 24 dist
26 } else { 25 } else {
27 dist.max(-ub) 26 dist.max(-ub)
28 } 27 }
29 } else 28 } else
30 /* ub ≥ 0.0*/ 29 /* lb ≥ 0.0*/
31 { 30 {
32 dist.max(lb) 31 dist.max(lb)
33 } 32 }
34 } 33 }
35 34
36 /// Returns the ∞-norm minimal subdifferential of $x ↦ (β/2)|x-y|_1^2 - g^⊤ x + λ\|x\|₁ +δ_{≥0}(x)$ at $x$. 35 /// Returns the ∞-norm minimal subdifferential of $x ↦ (β/2)|x-y|_1^2 - g^⊤ x + λ\|x\|₁ +δ_{≥0}(x)$ at $x$.
37 /// 36 ///
38 /// `v` will be modified and cannot be trusted to contain useful values afterwards. 37 /// `v` will be modified and cannot be trusted to contain useful values afterwards.
39 #[replace_float_literals(F::cast_from(literal))] 38 #[replace_float_literals(F::cast_from(literal))]
40 fn min_subdifferential<F: Float + nalgebra::RealField>( 39 fn min_subdifferential<F: Float + nalgebra::RealField, S1, S2, S3>(
41 y: &DVector<F>, 40 y: &Vector<F, Dyn, S1>,
42 x: &DVector<F>, 41 x: &Vector<F, Dyn, S2>,
43 g: &DVector<F>, 42 g: &Vector<F, Dyn, S3>,
44 λ: F, 43 λ: F,
45 β: F, 44 ) -> F
46 ) -> F { 45 where
46 S1: Storage<F, Dyn>,
47 S2: Storage<F, Dyn>,
48 S3: Storage<F, Dyn>,
49 ShapeConstraint: StridesOk<F, Dyn, U1, S2>,
50 {
47 let mut val = 0.0; 51 let mut val = 0.0;
48 let tmp = β * y.dist(x, L1); 52 let tmp = y.dist(x, L1);
49 for (&g_i, &x_i, y_i) in izip!(g.iter(), x.iter(), y.iter()) { 53 for (&g_i, &x_i, y_i) in izip!(g.iter(), x.iter(), y.iter()) {
50 let (mut lb, mut ub) = (-g_i, -g_i); 54 let (mut lb, mut ub) = (-g_i + λ, -g_i + λ);
51 match x_i.partial_cmp(y_i) { 55 if num_traits::abs(x_i - *y_i) < F::EPSILON {
52 Some(Greater) => { 56 lb -= tmp;
53 lb += tmp; 57 ub += tmp
54 ub += tmp 58 } else if x_i > *y_i {
55 } 59 lb += tmp;
56 Some(Less) => { 60 ub += tmp
57 lb -= tmp; 61 } else {
58 ub -= tmp 62 lb -= tmp;
59 } 63 ub -= tmp
60 Some(Equal) => { 64 }
61 lb -= tmp; 65 if x_i < F::EPSILON {
62 ub += tmp 66 lb = F::NEG_INFINITY;
63 } 67 }
64 None => {}
65 }
66 match x_i.partial_cmp(&0.0) {
67 Some(Greater) => {
68 lb += λ;
69 ub += λ
70 }
71 // Less should not happen
72 Some(Less | Equal) => {
73 lb = F::NEG_INFINITY;
74 ub += λ
75 }
76 None => {}
77 };
78 val = max_interval_dist_to_zero(val, lb, ub); 68 val = max_interval_dist_to_zero(val, lb, ub);
79 } 69 }
80 val 70 val
81 } 71 }
82 72
162 /// Since we do not actually know the unlocked elements, but just loop over all the possibilities 152 /// Since we do not actually know the unlocked elements, but just loop over all the possibilities
163 /// for them, we have to check that $λ$ is above the current lower bound for this parameter 153 /// for them, we have to check that $λ$ is above the current lower bound for this parameter
164 /// (`shift` in the code), and below a value that would cause changes in the locked set 154 /// (`shift` in the code), and below a value that would cause changes in the locked set
165 /// (`max_shift` in the code). 155 /// (`max_shift` in the code).
166 #[replace_float_literals(F::cast_from(literal))] 156 #[replace_float_literals(F::cast_from(literal))]
167 pub fn l1squared_nonneg_prox<F: Float + nalgebra::RealField>( 157 pub fn l1squared_nonneg_prox<F: Float + nalgebra::RealField, S1, S2>(
168 x: &mut DVector<F>, 158 x: &mut Vector<F, Dyn, S1>,
169 y: &DVector<F>, 159 y: &Vector<F, Dyn, S2>,
170 β: F, 160 β: F,
171 ) { 161 ) where
162 S2: Storage<F, Dyn>,
163 S1: StorageMut<F, Dyn>,
164 {
172 // nalgebra double-definition bullshit workaround 165 // nalgebra double-definition bullshit workaround
173 let abs = alg_tools::NumTraitsFloat::abs; 166 let abs = alg_tools::NumTraitsFloat::abs;
174 let min = alg_tools::NumTraitsFloat::min; 167 let min = alg_tools::NumTraitsFloat::min;
175 168
176 let mut λ = 0.0; 169 let mut λ = 0.0;
216 /// For detailed documentation of the inputs and outputs, refer to there. 209 /// For detailed documentation of the inputs and outputs, refer to there.
217 /// 210 ///
218 /// The `λ` component of the model is handled in the proximal step instead of the gradient step 211 /// The `λ` component of the model is handled in the proximal step instead of the gradient step
219 /// for potential performance improvements. 212 /// for potential performance improvements.
220 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())] 213 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())]
221 pub fn l1squared_nonneg_pp<F, I>( 214 pub fn l1squared_nonneg_pp<F, I, S1, S2, S3>(
222 y: &DVector<F::MixedType>, 215 y: &Vector<F::MixedType, Dyn, S1>,
223 g: &DVector<F::MixedType>, 216 g: &Vector<F::MixedType, Dyn, S2>,
224 λ_: F, 217 λ_: F,
225 β_: F, 218 x: &mut Vector<F::MixedType, Dyn, S3>,
226 x: &mut DVector<F::MixedType>,
227 τ_: F, 219 τ_: F,
228 θ_: F, 220 θ_: F,
229 iterator: I, 221 iterator: I,
230 ) -> usize 222 ) -> usize
231 where 223 where
232 F: Float + ToNalgebraRealField, 224 F: Float + ToNalgebraRealField,
233 I: AlgIteratorFactory<F>, 225 I: AlgIteratorFactory<F>,
226 S1: Storage<F::MixedType, Dyn>,
227 S2: Storage<F::MixedType, Dyn>,
228 S3: StorageMut<F::MixedType, Dyn>,
229 ShapeConstraint: StridesOk<F::MixedType, Dyn, U1, S3>,
234 { 230 {
235 let λ = λ_.to_nalgebra_mixed(); 231 let λ = λ_.to_nalgebra_mixed();
236 let β = β_.to_nalgebra_mixed();
237 let mut τ = τ_.to_nalgebra_mixed(); 232 let mut τ = τ_.to_nalgebra_mixed();
238 let θ = θ_.to_nalgebra_mixed(); 233 let θ = θ_.to_nalgebra_mixed();
239 let mut iters = 0; 234 let mut iters = 0;
240 235
241 iterator.iterate(|state| { 236 iterator.iterate(|state| {
242 // Primal step: x^{k+1} = prox_{(τβ/2)|.-y|_1^2+δ_{≥0}+}(x^k - τ(λ𝟙^⊤-g)) 237 // Primal step: x^{k+1} = prox_{(τβ/2)|.-y|_1^2+δ_{≥0}+}(x^k - τ(λ𝟙^⊤-g))
243 x.apply(|x_i| *x_i -= τ * λ); 238 x.apply(|x_i| *x_i -= τ * λ);
244 x.axpy(τ, g, 1.0); 239 x.axpy(τ, g, 1.0);
245 l1squared_nonneg_prox(x, y, τ * β); 240 l1squared_nonneg_prox(x, y, τ);
246 241
247 iters += 1; 242 iters += 1;
248 // This gives O(1/N^2) rates due to monotonicity of function values. 243 // This gives O(1/N^2) rates due to monotonicity of function values.
249 // Higher acceleration does not seem to be numerically stable. 244 // Higher acceleration does not seem to be numerically stable.
250 τ += θ; 245 τ += θ;
251 246
252 // This gives O(1/N^3) rates due to monotonicity of function values. 247 // This gives O(1/N^3) rates due to monotonicity of function values.
253 // Higher acceleration does not seem to be numerically stable. 248 // Higher acceleration does not seem to be numerically stable.
254 //τ + = F::cast_from(iters).to_nalgebra_mixed()*θ; 249 //τ + = F::cast_from(iters).to_nalgebra_mixed()*θ;
255 250
256 state.if_verbose(|| F::from_nalgebra_mixed(min_subdifferential(y, x, g, λ, β))) 251 state.if_verbose(|| F::from_nalgebra_mixed(min_subdifferential(y, x, g, λ)))
257 }); 252 });
258 253
259 iters 254 iters
260 } 255 }
261 256
264 /// 259 ///
265 /// The `λ` component of the model is handled in the proximal step instead of the gradient step 260 /// The `λ` component of the model is handled in the proximal step instead of the gradient step
266 /// for potential performance improvements. 261 /// for potential performance improvements.
267 /// The parameter `θ` is used to multiply the rescale the operator (identity) of the PDPS model. 262 /// The parameter `θ` is used to multiply the rescale the operator (identity) of the PDPS model.
268 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())] 263 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())]
269 pub fn l1squared_nonneg_pdps<F, I>( 264 pub fn l1squared_nonneg_pdps<F, I, S1, S2, S3>(
270 y: &DVector<F::MixedType>, 265 y: &Vector<F::MixedType, Dyn, S1>,
271 g: &DVector<F::MixedType>, 266 g: &Vector<F::MixedType, Dyn, S2>,
272 λ_: F, 267 λ_: F,
273 β_: F, 268 x: &mut Vector<F::MixedType, Dyn, S3>,
274 x: &mut DVector<F::MixedType>,
275 τ_: F, 269 τ_: F,
276 σ_: F, 270 σ_: F,
277 θ_: F, 271 θ_: F,
278 iterator: I, 272 iterator: I,
279 ) -> usize 273 ) -> usize
280 where 274 where
281 F: Float + ToNalgebraRealField, 275 F: Float + ToNalgebraRealField,
282 I: AlgIteratorFactory<F>, 276 I: AlgIteratorFactory<F>,
277 S1: Storage<F::MixedType, Dyn>,
278 S2: Storage<F::MixedType, Dyn>,
279 S3: StorageMut<F::MixedType, Dyn>,
280 ShapeConstraint: StridesOk<F::MixedType, Dyn, U1, S3>,
283 { 281 {
284 let λ = λ_.to_nalgebra_mixed(); 282 let λ = λ_.to_nalgebra_mixed();
285 let β = β_.to_nalgebra_mixed();
286 let τ = τ_.to_nalgebra_mixed(); 283 let τ = τ_.to_nalgebra_mixed();
287 let σ = σ_.to_nalgebra_mixed(); 284 let σ = σ_.to_nalgebra_mixed();
288 let θ = θ_.to_nalgebra_mixed(); 285 let θ = θ_.to_nalgebra_mixed();
289 let mut w = DVector::zeros(x.len()); 286 let mut w = DVector::zeros(x.len());
290 let mut tmp = DVector::zeros(x.len()); 287 let mut tmp = DVector::zeros(x.len());
291 let mut xprev = x.clone(); 288 let mut xprev = x.clone_owned();
292 let mut iters = 0; 289 let mut iters = 0;
293 290
294 iterator.iterate(|state| { 291 iterator.iterate(|state| {
295 // Primal step: x^{k+1} = prox_{(τβ/2)|.-y|_1^2}(x^k - τ (w^k - g)) 292 // Primal step: x^{k+1} = prox_{(τβ/2)|.-y|_1^2}(x^k - τ (w^k - g))
296 x.axpy(-τ * θ, &w, 1.0); 293 x.axpy(-τ * θ, &w, 1.0);
297 x.axpy(τ, g, 1.0); 294 x.axpy(τ, g, 1.0);
298 l1squared_prox(&mut tmp, x, y, τ * β); 295 l1squared_prox(&mut tmp, x, y, τ);
299 296
300 // Dual step: w^{k+1} = proj_{[-∞,λ]}(w^k + σ(2x^{k+1}-x^k)) 297 // Dual step: w^{k+1} = proj_{[-∞,λ]}(w^k + σ(2x^{k+1}-x^k))
301 w.axpy(2.0 * σ * θ, x, 1.0); 298 w.axpy(2.0 * σ * θ, x, 1.0);
302 w.axpy(-σ * θ, &xprev, 1.0); 299 w.axpy(-σ * θ, &xprev, 1.0);
303 w.apply(|w_i| *w_i = w_i.min(λ)); 300 w.apply(|w_i| *w_i = w_i.min(λ));
304 xprev.copy_from(x); 301 xprev.copy_from(x);
305 302
306 iters += 1; 303 iters += 1;
307 304
308 state.if_verbose(|| F::from_nalgebra_mixed(min_subdifferential(y, x, g, λ, β))) 305 state.if_verbose(|| F::from_nalgebra_mixed(min_subdifferential(y, x, g, λ)))
309 }); 306 });
310 307
311 iters 308 iters
312 } 309 }
313 310
321 /// for potential performance improvements. 318 /// for potential performance improvements.
322 /// The parameter `θ` is used to multiply the rescale the operator (identity) of the PDPS model. 319 /// The parameter `θ` is used to multiply the rescale the operator (identity) of the PDPS model.
323 /// We rewrite 320 /// We rewrite
324 /// <div>$$ 321 /// <div>$$
325 /// \begin{split} 322 /// \begin{split}
326 /// & \min_{x ∈ ℝ^n} \frac{β}{2} |x-y|_1^2 - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x) \\ 323 /// & \min_{x ∈ ℝ^n} \frac{1}{2} |x-y|_1^2 - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x) \\
327 /// & = \min_{x ∈ ℝ^n} \max_{w} ⟨θ w, x⟩ - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x) 324 /// & = \min_{x ∈ ℝ^n} \max_{w} ⟨θ w, x⟩ - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x)
328 /// - \left(x ↦ \frac{β}{2θ} |x-y|_1^2 \right)^*(w). 325 /// - \left(x ↦ \frac{1}{2θ} |x-y|_1^2 \right)^*(w).
329 /// \end{split} 326 /// \end{split}
330 /// $$</div> 327 /// $$</div>
331 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())] 328 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())]
332 pub fn l1squared_nonneg_pdps_alt<F, I>( 329 pub fn l1squared_nonneg_pdps_alt<F, I, S1, S2, S3>(
333 y: &DVector<F::MixedType>, 330 y: &Vector<F::MixedType, Dyn, S1>,
334 g: &DVector<F::MixedType>, 331 g: &Vector<F::MixedType, Dyn, S2>,
335 λ_: F, 332 λ_: F,
336 β_: F, 333 x: &mut Vector<F::MixedType, Dyn, S3>,
337 x: &mut DVector<F::MixedType>,
338 τ_: F, 334 τ_: F,
339 σ_: F, 335 σ_: F,
340 θ_: F, 336 θ_: F,
341 iterator: I, 337 iterator: I,
342 ) -> usize 338 ) -> usize
343 where 339 where
344 F: Float + ToNalgebraRealField, 340 F: Float + ToNalgebraRealField,
345 I: AlgIteratorFactory<F>, 341 I: AlgIteratorFactory<F>,
342 S1: Storage<F::MixedType, Dyn>,
343 S2: Storage<F::MixedType, Dyn>,
344 S3: StorageMut<F::MixedType, Dyn>,
345 ShapeConstraint: StridesOk<F::MixedType, Dyn, U1, S3>,
346 { 346 {
347 let λ = λ_.to_nalgebra_mixed(); 347 let λ = λ_.to_nalgebra_mixed();
348 let τ = τ_.to_nalgebra_mixed(); 348 let τ = τ_.to_nalgebra_mixed();
349 let σ = σ_.to_nalgebra_mixed(); 349 let σ = σ_.to_nalgebra_mixed();
350 let θ = θ_.to_nalgebra_mixed(); 350 let θ = θ_.to_nalgebra_mixed();
351 let β = β_.to_nalgebra_mixed();
352 let σθ = σ * θ; 351 let σθ = σ * θ;
353 let τθ = τ * θ; 352 let τλ = τ * λ;
353 let one_div_σθ = 1.0 / σθ;
354 let mut w = DVector::zeros(x.len()); 354 let mut w = DVector::zeros(x.len());
355 let mut tmp = DVector::zeros(x.len()); 355 let mut tmp = DVector::zeros(x.len());
356 let mut xprev = x.clone(); 356 let mut xprev = x.clone_owned();
357 let mut iters = 0; 357 let mut iters = 0;
358
359 let mut y_scale = y.clone_owned();
360 y_scale *= σ;
358 361
359 iterator.iterate(|state| { 362 iterator.iterate(|state| {
360 // Primal step: x^{k+1} = nonnegsoft_τλ(x^k - τ(θ w^k -g)) 363 // Primal step: x^{k+1} = nonnegsoft_τλ(x^k - τ(θ w^k -g))
361 x.axpy(-τθ, &w, 1.0); 364 if θ == 1.0 {
362 x.axpy(τ, g, 1.0); 365 for (x_i, xprev_i, w_i, &g_i) in
363 x.apply(|x_i| *x_i = nonneg_soft_thresholding(*x_i, τ * λ)); 366 izip!(x.iter_mut(), xprev.iter_mut(), w.iter_mut(), g.iter())
367 {
368 *x_i = nonneg_soft_thresholding(*x_i - τ * (*w_i - g_i), τλ);
369 // Fused dual part from below
370 *w_i += σ * (2.0 * *x_i - *xprev_i);
371 *xprev_i = *w_i;
372 }
373 } else {
374 for (x_i, xprev_i, w_i, &g_i) in
375 izip!(x.iter_mut(), xprev.iter_mut(), w.iter_mut(), g.iter())
376 {
377 *x_i = nonneg_soft_thresholding(*x_i - τ * (θ * *w_i - g_i), τλ);
378 // Fused dual part from below
379 *w_i += σ * (2.0 * *x_i - *xprev_i);
380 *xprev_i = *w_i;
381 }
382 }
383 // This is numerically unstable:
384 // x.axpy(-τθ, &w, 1.0);
385 // x.axpy(τ, g, 1.0);
386 // x.apply(|x_i| *x_i = nonneg_soft_thresholding(*x_i, τ * λ));
364 387
365 // Dual step: with g(x) = (β/(2θ))‖x-y‖₁² and q = w^k + σ(2x^{k+1}-x^k), 388 // Dual step: with g(x) = (β/(2θ))‖x-y‖₁² and q = w^k + σ(2x^{k+1}-x^k),
366 // we compute w^{k+1} = prox_{σg^*}(q) for 389 // we compute w^{k+1} = prox_{σg^*}(q) for
367 // = q - σ prox_{g/σ}(q/σ) 390 // = q - σ prox_{g/σ}(q/σ)
368 // = q - σ prox_{(β/(2θσ))‖.-y‖₁²}(q/σ) 391 // = q - σ prox_{(β/(2θσ))‖.-y‖₁²}(q/σ)
369 // = σ(q/σ - prox_{(β/(2θσ))‖.-y‖₁²}(q/σ)) 392 // = σ(q/σ - prox_{(β/(2θσ))‖.-y‖₁²}(q/σ))
393 // ALT
394 // = q - prox_{(β/(2θσ))‖.-σy‖₁²}(q)
370 // where q/σ = w^k/σ + (2x^{k+1}-x^k), 395 // where q/σ = w^k/σ + (2x^{k+1}-x^k),
371 w /= σ; 396
372 w.axpy(2.0, x, 1.0); 397 // This has been fused into the loop below
373 w.axpy(-1.0, &xprev, 1.0); 398 // for (xprev_i, w_i, &x_i) in izip!(xprev.iter_mut(), w.iter_mut(), x.iter()) {
374 xprev.copy_from(&w); // use xprev as temporary variable 399 // *w_i += σ * (2.0 * x_i - *xprev_i);
375 l1squared_prox(&mut tmp, &mut xprev, y, β / σθ); 400 // *xprev_i = *w_i;
401 // }
402 // xprev.axpy(2.0, x, -1.0);
403 // w.axpy(σ, &xprev, 1.0);
404 // xprev.copy_from(&w); // use xprev as temporary variable
405 //l1squared_prox(&mut tmp, &mut xprev, &y_scale, β_div_σθ);
406 l1squared_prox(&mut tmp, &mut xprev, &y_scale, one_div_σθ);
376 w -= &xprev; 407 w -= &xprev;
377 w *= σ;
378 xprev.copy_from(x); 408 xprev.copy_from(x);
379 409
380 iters += 1; 410 iters += 1;
381 411
382 state.if_verbose(|| F::from_nalgebra_mixed(min_subdifferential(y, x, g, λ, β))) 412 state.if_verbose(|| F::from_nalgebra_mixed(min_subdifferential(y, x, g, λ)))
383 }); 413 });
384 414
385 iters 415 iters
416 }
417
418 /// This is an exact solver for
419 /// <div>$$
420 /// \min_{x ∈ ℝ^n} \frac{1}{2} |x-y|_1^2 - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x).
421 /// $$</div>
422 /// i.e.,
423 /// <div>$$
424 /// \min_{x ∈ ℝ^n} \frac{1}{2} |x-y|_1^2 + (λ - g)^⊤ x + δ_{≥ 0}(x),
425 /// $$</div>
426 /// which has the optimality conditions
427 /// <div>$$
428 /// 0 ∈ |x-y|_1\sign(x-y)_i + λ - g_i + δ_{≥ 0}(x_i)
429 /// \quad\text{for all}\quad i.
430 /// $$</div>
431 ///
432 /// If $x_i > 0$ and $x_i ≠ y_i$, then this forces $|g_i -λ| = |x-y|_1$.
433 /// Let $i^*$ be such an index. Then if $i ≠ i^*$ does not have the same value of $|g_i-λ|$,
434 /// we *must* have $x_i=y_i$ or $x_i=0$. In fact, even $x_i=y_i$ is impossible for $y_i>0$ if
435 /// $|g_i-λ| > |g_{i^*}-λ|$, whereas, otherwise $x_i=y_i$ is possible if $y_i>=0$.
436 /// If $|g_i-λ| > |g_{i^*}-λ|$, we must, therefore, either have $x_i=0$ or be able to take $i^*=i$.
437 ///
438 /// If $|g_i-λ| ≤ |g_{i^*}-λ|$, we can have either $x_i=0$ or $x_i=y_i$. If $y_i<=0$, then,
439 /// of course $x_i=0$. If $y_i>0$, we can take $x_i=y_i$.
440 ///
441 /// We, therefore, sort the |g_i-λ|, and look for an index $i^*$ that gives a non-contradictory
442 /// $β|x-y|_1=|g_i -λ|$, noting that contributions to β|x-y|_1= come from, besides $i^*$, from
443 /// indices $i$ such that $x_i= 0 ≠ y_i$.
444 ///
445 /// Finally, if no index $i$ reaches $|g_i -λ| = |x-y|_1$, we have to $|x-y|_1$ being in an
446 /// intermediate interval. This can similarly done using sorting.
447 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())]
448 pub(super) fn l1squared_nonneg_solve_exact<F, S1, S2, S3>(
449 y: &Vector<F::MixedType, Dyn, S1>,
450 g: &Vector<F::MixedType, Dyn, S2>,
451 λ_: F,
452 x: &mut Vector<F::MixedType, Dyn, S3>,
453 ) -> bool
454 where
455 F: Float + ToNalgebraRealField,
456 S1: Storage<F::MixedType, Dyn>,
457 S2: Storage<F::MixedType, Dyn>,
458 S3: StorageMut<F::MixedType, Dyn>,
459 ShapeConstraint: StridesOk<F::MixedType, Dyn, U1, S3>,
460 {
461 let λ = λ_.to_nalgebra_mixed();
462
463 assert_eq!(y.len(), g.len());
464 assert_eq!(x.len(), g.len());
465 assert!(x.len() <= u32::MAX as usize);
466
467 #[derive(Debug)]
468 struct Tmp<F> {
469 y: F,
470 d: F,
471 contrib: F,
472 i: u32, // Shrink size for sort
473 λ_le_g: bool,
474 }
475 let mut sorted = Vec::from_iter(izip!(g.iter(), y.iter(), 0..).map(|(&g_i, &y_i, i)| {
476 // We already precompute the comparison λ <= g_i here, since we need it for the abs,
477 // and would need to store g_i for that in any case.
478 let (d, λ_le_g) = if λ <= g_i {
479 (g_i - λ, true)
480 } else {
481 (λ - g_i, false)
482 };
483 Tmp { d, λ_le_g, y: y_i, i, contrib: 0.0 }
484 }));
485 sorted
486 .as_mut_slice()
487 .sort_unstable_by(|a, b| b.d.total_cmp(&a.d));
488
489 // Reverse-compute contribs
490 sorted.iter_mut().rev().fold(0.0, |contrib, a| {
491 a.contrib = contrib;
492 if a.y < 0.0 {
493 contrib - a.y
494 } else {
495 contrib
496 }
497 });
498
499 let mut contrib0 = 0.0;
500
501 x.fill(0.0);
502
503 let mut it = sorted.iter();
504 let mut found = false;
505
506 // We first try to find an index m such that y_m ≠ x_m > 0.0 that satisfies ‖x-y‖₁ = |λ - g_m|.
507 'search: while let Some(m) = it.next() {
508 let d_m = m.d;
509 let y_m = m.y;
510 let contrib = m.contrib + contrib0;
511 let δ = d_m - contrib;
512 if δ > 0.0 {
513 // If λ < g[m], we must have x[m]≥y[m] so (contrib + x[m]-y[m])=g[m]-λ=d
514 // If λ = g_m, then also d_m=0, so contrib=0, and this gtives *x_m=y_m.
515 // If λ > g[m], We must have x[m]≤y[m] so -(contrib + y[m]-x[m])=g[m]-λ=-d.
516 let x_m_prime = if m.λ_le_g { y_m + δ } else { y_m - δ };
517 if x_m_prime > 0.0 {
518 let x_m = unsafe { x.get_unchecked_mut(m.i as usize) };
519 *x_m = x_m_prime;
520 found = true;
521 break 'search;
522 }
523 }
524 contrib0 += num_traits::abs(y_m);
525 }
526
527 if !found {
528 // Every x_i is either zero or equal to y_i.
529 // We scan intervals (bound,prev_bound) between the d-values of each component,
530 // for the value of ‖x-y‖₁. Then it can be decided whether x_i should be one or equal to
531 // y_i. Due to the sorting and pre-calculation of posterior contributions, this simplifies
532 // into the following:
533 let mut prev_bound = F::MixedType::INFINITY;
534 let mut contrib0 = 0.0;
535 it = sorted.iter();
536 'search_degenerate: while let Some(m) = it.next() {
537 let bound = m.d;
538 let contrib = m.contrib + contrib0 - m.y.min(0.0);
539 if prev_bound >= contrib && contrib >= bound {
540 if m.y > 0.0 {
541 let x_m = unsafe { x.get_unchecked_mut(m.i as usize) };
542 *x_m = m.y;
543 }
544 found = true;
545 break 'search_degenerate;
546 }
547 prev_bound = bound;
548 if m.y > 0.0 {
549 contrib0 += m.y;
550 }
551 }
552 // Check final interval from last element to -∞, if nothing found.
553 if !found && !(prev_bound >= contrib0) {
554 panic!("l1squared_nonneg_solve_exact failure")
555 }
556 }
557
558 // Set remaining components to their now known values
559 while let Some(a) = it.next() {
560 // Safety: size checked above.
561 let y_i = a.y;
562 if y_i >= 0.0 {
563 // We can leave unchanged, as ‖x-y‖₁ is guaranteed large enough if
564 // the current maximising index attempt succeeds.
565 let x_i = unsafe { x.get_unchecked_mut(a.i as usize) };
566 *x_i = y_i
567 }
568 // Zero fill in `else` case done above in init already
569 }
570
571 debug_assert!(min_subdifferential(y, x, g, λ) <= F::EPSILON.to_nalgebra_mixed() * 10.0);
572
573 return true;
386 } 574 }
387 575
388 /// This function applies an iterative method for the solution of the problem 576 /// This function applies an iterative method for the solution of the problem
389 /// <div>$$ 577 /// <div>$$
390 /// \min_{x ∈ ℝ^n} \frac{β}{2} |x-y|_1^2 - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x). 578 /// \min_{x ∈ ℝ^n} \frac{1}{2} |x-y|_1^2 - g^⊤ x + λ\|x\|₁ + δ_{≥ 0}(x).
391 /// $$</div> 579 /// $$</div>
392 /// 580 ///
393 /// This function returns the number of iterations taken. 581 /// This function returns the number of iterations taken.
394 #[replace_float_literals(F::cast_from(literal))] 582 #[replace_float_literals(F::cast_from(literal))]
395 pub fn l1squared_nonneg<F, I>( 583 pub fn l1squared_nonneg<F, I, S1, S2, S3>(
396 y: &DVector<F::MixedType>, 584 y: &Vector<F::MixedType, Dyn, S1>,
397 g: &DVector<F::MixedType>, 585 g: &Vector<F::MixedType, Dyn, S2>,
398 λ: F, 586 λ: F,
399 β: F, 587 x: &mut Vector<F::MixedType, Dyn, S3>,
400 x: &mut DVector<F::MixedType>,
401 inner: &InnerSettings<F>, 588 inner: &InnerSettings<F>,
402 iterator: I, 589 iterator: I,
403 ) -> usize 590 ) -> usize
404 where 591 where
405 F: Float + ToNalgebraRealField, 592 F: Float + ToNalgebraRealField,
406 I: AlgIteratorFactory<F>, 593 I: AlgIteratorFactory<F>,
594 S1: Storage<F::MixedType, Dyn>,
595 S2: Storage<F::MixedType, Dyn>,
596 S3: StorageMut<F::MixedType, Dyn>,
597 ShapeConstraint: StridesOk<F::MixedType, Dyn, U1, S3>,
407 { 598 {
599 if let InnerMethod::Exact = inner.method {
600 // Try exact solution, fall back to PDPS if it does not work.
601 if l1squared_nonneg_solve_exact(y, g, λ, x) {
602 return 1;
603 }
604 }
605
408 match inner.method { 606 match inner.method {
409 InnerMethod::PDPS => { 607 InnerMethod::PDPS | InnerMethod::Exact => {
410 let inner_θ = 1.0; 608 let inner_θ = 1.0;
411 // Estimate of ‖K‖ for K=θ\Id. 609 //Estimate of ‖K‖ for K=θ\Id.
412 let normest = inner_θ; 610 let normest = inner_θ;
413 let (inner_τ, inner_σ) = (inner.pdps_τσ0.0 / normest, inner.pdps_τσ0.1 / normest); 611 let (inner_τ, inner_σ) = (inner.pdps_τσ0.0 / normest, inner.pdps_τσ0.1 / normest);
414 l1squared_nonneg_pdps_alt(y, g, λ, β, x, inner_τ, inner_σ, inner_θ, iterator) 612 l1squared_nonneg_pdps_alt(y, g, λ, x, inner_τ, inner_σ, inner_θ, iterator)
415 } 613 }
416 InnerMethod::PP | InnerMethod::FB => { 614 InnerMethod::PP | InnerMethod::FB => {
417 let inner_τ = inner.pp_τ.0; 615 let inner_τ = inner.pp_τ.0;
418 let inner_θ = inner.pp_τ.1; 616 let inner_θ = inner.pp_τ.1;
419 l1squared_nonneg_pp(y, g, λ, β, x, inner_τ, inner_θ, iterator) 617 l1squared_nonneg_pp(y, g, λ, x, inner_τ, inner_θ, iterator)
420 } 618 }
421 other => unimplemented!("${other:?} is unimplemented"), 619 other => unimplemented!("${other:?} is unimplemented"),
422 } 620 }
423 } 621 }

mercurial