| 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 |
| 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 } |