4 |
4 |
5 use nalgebra::{DVector, DMatrix}; |
5 use nalgebra::{DVector, DMatrix}; |
6 use numeric_literals::replace_float_literals; |
6 use numeric_literals::replace_float_literals; |
7 use itertools::{izip, Itertools}; |
7 use itertools::{izip, Itertools}; |
8 use colored::Colorize; |
8 use colored::Colorize; |
|
9 use std::cmp::Ordering::*; |
9 |
10 |
10 use alg_tools::iter::Mappable; |
11 use alg_tools::iter::Mappable; |
11 use alg_tools::error::NumericalError; |
12 use alg_tools::error::NumericalError; |
12 use alg_tools::iterate::{ |
13 use alg_tools::iterate::{ |
13 AlgIteratorFactory, |
14 AlgIteratorFactory, |
20 use crate::types::*; |
21 use crate::types::*; |
21 use super::{ |
22 use super::{ |
22 InnerMethod, |
23 InnerMethod, |
23 InnerSettings |
24 InnerSettings |
24 }; |
25 }; |
|
26 use super::l1squared_nonneg::max_interval_dist_to_zero; |
25 |
27 |
26 /// Compute the proximal operator of $x \mapsto x + \delta\_{[0, \infty)}$, i.e., |
28 /// Compute the proximal operator of $x \mapsto x + \delta\_{[0, \infty)}$, i.e., |
27 /// the non-negativity contrained soft-thresholding operator. |
29 /// the non-negativity contrained soft-thresholding operator. |
28 #[inline] |
30 #[inline] |
29 #[replace_float_literals(F::cast_from(literal))] |
31 #[replace_float_literals(F::cast_from(literal))] |
30 fn nonneg_soft_thresholding<F : Float>(v : F, λ : F) -> F { |
32 pub(super) fn nonneg_soft_thresholding<F : Float>(v : F, λ : F) -> F { |
31 (v - λ).max(0.0) |
33 (v - λ).max(0.0) |
32 } |
34 } |
33 |
35 |
34 /// Returns the ∞-norm minimal subdifferential of $x ↦ x^⊤Ax - g^⊤ x + λ\vec 1^⊤ x δ_{≥ 0}(x)$ |
36 /// Returns the ∞-norm minimal subdifferential of $x ↦ x^⊤Ax/2 - g^⊤ x + λ\vec 1^⊤ x + δ_{≥ 0}(x)$ |
35 /// at $x$. |
37 /// at $x$. |
36 /// |
38 /// |
37 /// `v` will be modified and cannot be trusted to contain useful values afterwards. |
39 /// `v` will be modified and cannot be trusted to contain useful values afterwards. |
38 #[replace_float_literals(F::cast_from(literal).to_nalgebra_mixed())] |
40 #[replace_float_literals(F::cast_from(literal))] |
39 fn min_subdifferential<F : Float + ToNalgebraRealField>( |
41 fn min_subdifferential<F : Float + nalgebra::RealField>( |
40 v : &mut DVector<F::MixedType>, |
42 v : &mut DVector<F>, |
41 mA : &DMatrix<F::MixedType>, |
43 mA : &DMatrix<F>, |
42 x : &DVector<F::MixedType>, |
44 x : &DVector<F>, |
43 g : &DVector<F::MixedType>, |
45 g : &DVector<F>, |
44 λ : F::MixedType |
46 λ : F |
45 ) -> F { |
47 ) -> F { |
46 v.copy_from(g); |
48 v.copy_from(g); |
47 mA.gemv(v, 1.0, x, -1.0); // v = Ax - g |
49 mA.gemv(v, 1.0, x, -1.0); // v = Ax - g |
48 let mut val = 0.0; |
50 let mut val = 0.0; |
49 for (&v_i, &x_i) in izip!(v.iter(), x.iter()) { |
51 for (&v_i, &x_i) in izip!(v.iter(), x.iter()) { |
50 // The subdifferential of the objective is $Ax - g + λ + ∂ δ_{≥ 0}(x)$. |
52 let (mut lb, mut ub) = (v_i, v_i); |
51 let d = v_i + λ; |
53 match x_i.partial_cmp(&0.0) { |
52 if x_i > 0.0 || d < 0.0 { |
54 Some(Greater) => { lb += λ; ub += λ }, |
53 val = val.max(d.abs()); |
55 // Less should not happen |
|
56 Some(Less|Equal) => { lb = F::NEG_INFINITY; ub += λ }, |
|
57 None => {}, |
54 } |
58 } |
|
59 val = max_interval_dist_to_zero(val, lb, ub); |
55 } |
60 } |
56 F::from_nalgebra_mixed(val) |
61 val |
57 } |
62 } |
58 |
63 |
59 /// Forward-backward splitting implementation of [`quadratic_nonneg`]. |
64 /// Forward-backward splitting implementation of [`quadratic_nonneg`]. |
60 /// For detailed documentation of the inputs and outputs, refer to there. |
65 /// For detailed documentation of the inputs and outputs, refer to there. |
61 /// |
66 /// |
279 iters += 1; |
284 iters += 1; |
280 |
285 |
281 // 4. Report solution quality |
286 // 4. Report solution quality |
282 state.if_verbose(|| { |
287 state.if_verbose(|| { |
283 // Calculate subdifferential at the FB step `x` that hasn't yet had `s` yet added. |
288 // Calculate subdifferential at the FB step `x` that hasn't yet had `s` yet added. |
284 min_subdifferential(&mut v, mA, x, g, λ) |
289 F::from_nalgebra_mixed(min_subdifferential(&mut v, mA, x, g, λ)) |
285 }) |
290 }) |
286 }); |
291 }); |
287 |
292 |
288 res.map(|_| iters) |
293 res.map(|_| iters) |
289 } |
294 } |
290 |
295 |
291 /// This function applies an iterative method for the solution of the quadratic non-negativity |
296 /// This function applies an iterative method for the solution of the quadratic non-negativity |
292 /// constrained problem |
297 /// constrained problem |
293 /// <div>$$ |
298 /// <div>$$ |
294 /// \min_{x ∈ ℝ^n} \frac{1}{2} x^⊤Ax - g^⊤ x + λ{\vec 1}^⊤ x + c + δ_{≥ 0}(x). |
299 /// \min_{x ∈ ℝ^n} \frac{1}{2} x^⊤Ax - g^⊤ x + λ{\vec 1}^⊤ x + δ_{≥ 0}(x). |
295 /// $$</div> |
300 /// $$</div> |
296 /// Semismooth Newton or forward-backward are supported based on the setting in `method`. |
301 /// Semismooth Newton or forward-backward are supported based on the setting in `method`. |
297 /// The parameter `mA` is matrix $A$, and `g` and `λ` are as in the mathematical formulation. |
302 /// The parameter `mA` is matrix $A$, and `g` and `λ` are as in the mathematical formulation. |
298 /// The constant $c$ does not need to be provided. The step length parameter is `τ` while |
303 /// The constant $c$ does not need to be provided. The step length parameter is `τ` while |
299 /// `x` contains the initial iterate and on return the final one. The `iterator` controls |
304 /// `x` contains the initial iterate and on return the final one. The `iterator` controls |
305 /// * Valkonen T. - _A method for weighted projections to the positive definite |
310 /// * Valkonen T. - _A method for weighted projections to the positive definite |
306 /// cone_, <https://doi.org/10.1080/02331934.2014.929680>. |
311 /// cone_, <https://doi.org/10.1080/02331934.2014.929680>. |
307 /// |
312 /// |
308 /// This function returns the number of iterations taken. |
313 /// This function returns the number of iterations taken. |
309 pub fn quadratic_nonneg<F, I>( |
314 pub fn quadratic_nonneg<F, I>( |
310 method : InnerMethod, |
|
311 mA : &DMatrix<F::MixedType>, |
315 mA : &DMatrix<F::MixedType>, |
312 g : &DVector<F::MixedType>, |
316 g : &DVector<F::MixedType>, |
313 //c_ : F, |
|
314 λ : F, |
317 λ : F, |
315 x : &mut DVector<F::MixedType>, |
318 x : &mut DVector<F::MixedType>, |
316 τ : F, |
319 mA_normest : F, |
|
320 inner : &InnerSettings<F>, |
317 iterator : I |
321 iterator : I |
318 ) -> usize |
322 ) -> usize |
319 where F : Float + ToNalgebraRealField, |
323 where F : Float + ToNalgebraRealField, |
320 I : AlgIteratorFactory<F> |
324 I : AlgIteratorFactory<F> |
321 { |
325 { |
322 |
326 let inner_τ = inner.fb_τ0 / mA_normest; |
323 match method { |
327 |
324 InnerMethod::FB => |
328 match inner.method { |
325 quadratic_nonneg_fb(mA, g, λ, x, τ, iterator), |
329 InnerMethod::FB | InnerMethod::Default => |
|
330 quadratic_nonneg_fb(mA, g, λ, x, inner_τ, iterator), |
326 InnerMethod::SSN => |
331 InnerMethod::SSN => |
327 quadratic_nonneg_ssn(mA, g, λ, x, τ, iterator).unwrap_or_else(|e| { |
332 quadratic_nonneg_ssn(mA, g, λ, x, inner_τ, iterator).unwrap_or_else(|e| { |
328 println!("{}", format!("{e}. Using FB fallback.").red()); |
333 println!("{}", format!("{e}. Using FB fallback.").red()); |
329 let ins = InnerSettings::<F>::default(); |
334 let ins = InnerSettings::<F>::default(); |
330 quadratic_nonneg_fb(mA, g, λ, x, τ, ins.iterator_options) |
335 quadratic_nonneg_fb(mA, g, λ, x, inner_τ, ins.iterator_options) |
331 }) |
336 }), |
|
337 InnerMethod::PDPS => unimplemented!(), |
332 } |
338 } |
333 } |
339 } |