optimization_solvers/newton/
spn.rs1use super::*;
9
10#[derive(derive_getters::Getters)]
11pub struct SpectralProjectedNewton {
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 SpectralProjectedNewton {
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 SpectralProjectedNewton {
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 SpectralProjectedNewton {
77 fn compute_direction(
78 &mut self,
79 eval: &FuncEvalMultivariate,
80 ) -> Result<DVector<Floating>, SolverError> {
81 let hessian = eval
83 .hessian()
84 .clone()
85 .expect("Hessian not available in the oracle");
86 let direction = &self.x - self.lambda * &hessian.cholesky().unwrap().solve(eval.g());
87 let direction = direction.box_projection(&self.lower_bound, &self.upper_bound);
88 let direction = direction - &self.x;
89 Ok(direction)
90 }
91}
92
93impl LineSearchSolver for SpectralProjectedNewton {
94 fn has_converged(&self, eval: &FuncEvalMultivariate) -> bool {
95 let projected_gradient = self.projected_gradient(eval);
96 projected_gradient.infinity_norm() < self.grad_tol
97 }
98 fn xk(&self) -> &DVector<Floating> {
99 &self.x
100 }
101 fn xk_mut(&mut self) -> &mut DVector<Floating> {
102 &mut self.x
103 }
104 fn k(&self) -> &usize {
105 &self.k
106 }
107 fn k_mut(&mut self) -> &mut usize {
108 &mut self.k
109 }
110
111 fn update_next_iterate<LS: LineSearch>(
112 &mut self,
113 line_search: &mut LS,
114 eval_x_k: &FuncEvalMultivariate, oracle: &mut impl FnMut(&DVector<Floating>) -> FuncEvalMultivariate,
116 direction: &DVector<Floating>,
117 max_iter_line_search: usize,
118 ) -> Result<(), SolverError> {
119 let step = line_search.compute_step_len(
120 self.xk(),
121 eval_x_k,
122 direction,
123 oracle,
124 max_iter_line_search,
125 );
126
127 let xk = self.xk(); debug!(target: "spectral_projected_newton", "ITERATE: {} + {} * {} = {}", xk, step, direction, xk + step * direction);
130
131 let next_iterate = xk + step * direction;
132
133 let s_k = &next_iterate - xk;
135 let y_k = oracle(&next_iterate).g() - eval_x_k.g();
136
137 *self.xk_mut() = next_iterate;
138
139 let skyk = s_k.dot(&y_k);
141 if skyk <= 0. {
142 debug!(target: "spectral_projected_newton", "skyk = {} <= 0. Resetting lambda to lambda_max", skyk);
143 self.lambda = self.lambda_max;
144 return Ok(());
145 }
146 let sksk = s_k.dot(&s_k);
147 self.lambda = (sksk / skyk).min(self.lambda_max).max(self.lambda_min);
148 Ok(())
149 }
150}
151
152#[cfg(test)]
153mod spg_test {
154 use super::*;
155 #[test]
156 pub fn constrained_spg_backtracking() {
157 std::env::set_var("RUST_LOG", "info");
158
159 let _ = Tracer::default()
160 .with_stdout_layer(Some(LogFormat::Normal))
161 .build();
162 let gamma = 1e9;
163 let mut f_and_g = |x: &DVector<Floating>| -> FuncEvalMultivariate {
164 let f = 0.5 * (x[0].powi(2) + gamma * x[1].powi(2));
165 let g = DVector::from(vec![x[0], gamma * x[1]]);
166 let hessian = DMatrix::from_iterator(2, 2, vec![1.0, 0.0, 0.0, gamma]);
167 FuncEvalMultivariate::from((f, g)).with_hessian(hessian)
168 };
169
170 let lower_bounds = DVector::from_vec(vec![-1., 47.]);
172 let upper_oounds = DVector::from_vec(vec![f64::INFINITY, f64::INFINITY]);
173 let c1 = 1e-4;
175 let m = 10;
176 let mut ls = GLLQuadratic::new(c1, m);
178
179 let tol = 1e-12;
181 let x_0 = DVector::from(vec![180.0, 152.0]);
182 let mut gd =
183 SpectralProjectedNewton::new(tol, x_0, &mut f_and_g, lower_bounds, upper_oounds);
184
185 let max_iter_solver = 10000;
187 let max_iter_line_search = 1000;
188
189 gd.minimize(
190 &mut ls,
191 f_and_g,
192 max_iter_solver,
193 max_iter_line_search,
194 None,
195 )
196 .unwrap();
197
198 println!("Iterate: {:?}", gd.xk());
199
200 let eval = f_and_g(gd.xk());
201 println!("Function eval: {:?}", eval);
202 println!(
203 "Projected Gradient norm: {:?}",
204 gd.projected_gradient(&eval).norm()
205 );
206 println!("tol: {:?}", tol);
207
208 let convergence = gd.has_converged(&eval);
209 println!("Convergence: {:?}", convergence);
210 }
211}