optimization_solvers/steepest_descent/
spg.rs1use super::*;
9
10#[derive(derive_getters::Getters)]
11pub struct SpectralProjectedGradient {
12 grad_tol: Floating,
13 x: DVector<Floating>,
14 k: usize,
15 lower_bound: DVector<Floating>,
16 upper_bound: DVector<Floating>,
17 lambda: Floating,
18 lambda_min: Floating,
19 lambda_max: Floating,
20}
21
22impl SpectralProjectedGradient {
23 pub fn with_lambdas(mut self, lambda_min: Floating, lambda_max: Floating) -> Self {
24 self.lambda_min = lambda_min;
25 self.lambda_max = lambda_max;
26 self
27 }
28 pub fn new(
29 grad_tol: Floating,
30 x0: DVector<Floating>,
31 oracle: &mut impl FnMut(&DVector<Floating>) -> FuncEvalMultivariate,
32 lower_bound: DVector<Floating>,
33 upper_bound: DVector<Floating>,
34 ) -> Self {
35 let x0 = x0.box_projection(&lower_bound, &upper_bound);
36 let lambda_min = 1e-3;
37 let lambda_max = 1e3;
38
39 let eval0 = oracle(&x0);
41 let direction0 = &x0 - eval0.g();
42 let direction0 = direction0.box_projection(&lower_bound, &upper_bound);
43 let direction0 = direction0 - &x0;
44 let lambda = (1. / direction0.infinity_norm())
45 .min(lambda_max)
46 .max(lambda_min);
47
48 Self {
49 grad_tol,
50 x: x0,
51 k: 0,
52 lower_bound,
53 upper_bound,
54 lambda,
55 lambda_min,
56 lambda_max,
57 }
58 }
59}
60
61impl HasBounds for SpectralProjectedGradient {
62 fn lower_bound(&self) -> &DVector<Floating> {
63 &self.lower_bound
64 }
65 fn set_lower_bound(&mut self, lower_bound: DVector<Floating>) {
66 self.lower_bound = lower_bound;
67 }
68 fn set_upper_bound(&mut self, upper_bound: DVector<Floating>) {
69 self.upper_bound = upper_bound;
70 }
71 fn upper_bound(&self) -> &DVector<Floating> {
72 &self.upper_bound
73 }
74}
75
76impl ComputeDirection for SpectralProjectedGradient {
77 fn compute_direction(
78 &mut self,
79 eval: &FuncEvalMultivariate,
80 ) -> Result<DVector<Floating>, SolverError> {
81 let direction = &self.x - self.lambda * eval.g();
82 let direction = direction.box_projection(&self.lower_bound, &self.upper_bound);
83 let direction = direction - &self.x;
84 Ok(direction)
85 }
86}
87
88impl LineSearchSolver for SpectralProjectedGradient {
89 fn has_converged(&self, eval: &FuncEvalMultivariate) -> bool {
90 let projected_gradient = self.projected_gradient(eval);
91 projected_gradient.infinity_norm() < self.grad_tol
92 }
93 fn xk(&self) -> &DVector<Floating> {
94 &self.x
95 }
96 fn xk_mut(&mut self) -> &mut DVector<Floating> {
97 &mut self.x
98 }
99 fn k(&self) -> &usize {
100 &self.k
101 }
102 fn k_mut(&mut self) -> &mut usize {
103 &mut self.k
104 }
105
106 fn update_next_iterate<LS: LineSearch>(
107 &mut self,
108 line_search: &mut LS,
109 eval_x_k: &FuncEvalMultivariate, oracle: &mut impl FnMut(&DVector<Floating>) -> FuncEvalMultivariate,
111 direction: &DVector<Floating>,
112 max_iter_line_search: usize,
113 ) -> Result<(), SolverError> {
114 let step = line_search.compute_step_len(
115 self.xk(),
116 eval_x_k,
117 direction,
118 oracle,
119 max_iter_line_search,
120 );
121
122 let xk = self.xk(); debug!(target: "spectral_projected_gradient", "ITERATE: {} + {} * {} = {}", xk, step, direction, xk + step * direction);
125
126 let next_iterate = xk + step * direction;
127
128 let s_k = &next_iterate - xk;
130 let y_k = oracle(&next_iterate).g() - eval_x_k.g();
131
132 *self.xk_mut() = next_iterate;
133
134 let skyk = s_k.dot(&y_k);
136 if skyk <= 0. {
137 debug!(target: "spectral_projected_gradient", "skyk = {} <= 0. Resetting lambda to lambda_max", skyk);
138 self.lambda = self.lambda_max;
139 return Ok(());
140 }
141 let sksk = s_k.dot(&s_k);
142 self.lambda = (sksk / skyk).min(self.lambda_max).max(self.lambda_min);
143 Ok(())
144 }
145}
146
147#[cfg(test)]
148mod spg_test {
149 use super::*;
150 #[test]
151 pub fn constrained_spg_backtracking() {
152 std::env::set_var("RUST_LOG", "info");
153
154 let _ = Tracer::default()
155 .with_stdout_layer(Some(LogFormat::Normal))
156 .build();
157 let gamma = 1e9;
158 let mut f_and_g = |x: &DVector<Floating>| -> FuncEvalMultivariate {
159 let f = 0.5 * (x[0].powi(2) + gamma * x[1].powi(2));
160 let g = DVector::from(vec![x[0], gamma * x[1]]);
161 (f, g).into()
162 };
163
164 let lower_bounds = DVector::from_vec(vec![-1., 47.]);
166 let upper_oounds = DVector::from_vec(vec![f64::INFINITY, f64::INFINITY]);
167 let c1 = 1e-4;
169 let m = 10;
170 let mut ls = GLLQuadratic::new(c1, m);
172
173 let tol = 1e-12;
175 let x_0 = DVector::from(vec![180.0, 152.0]);
176 let mut gd =
177 SpectralProjectedGradient::new(tol, x_0, &mut f_and_g, lower_bounds, upper_oounds);
178
179 let max_iter_solver = 10000;
181 let max_iter_line_search = 1000;
182
183 gd.minimize(
184 &mut ls,
185 f_and_g,
186 max_iter_solver,
187 max_iter_line_search,
188 None,
189 )
190 .unwrap();
191
192 println!("Iterate: {:?}", gd.xk());
193
194 let eval = f_and_g(gd.xk());
195 println!("Function eval: {:?}", eval);
196 println!(
197 "Projected Gradient norm: {:?}",
198 gd.projected_gradient(&eval).norm()
199 );
200 println!("tol: {:?}", tol);
201
202 let convergence = gd.has_converged(&eval);
203 println!("Convergence: {:?}", convergence);
204 }
205}