optimization_solvers/quasi_newton/
broyden_b.rs1use super::*;
2
3#[derive(derive_getters::Getters)]
4pub struct BroydenB {
5 approx_inv_hessian: DMatrix<Floating>,
6 x: DVector<Floating>,
7 k: usize,
8 tol: Floating,
9 s_norm: Option<Floating>,
10 y_norm: Option<Floating>,
11 identity: DMatrix<Floating>,
12 lower_bound: DVector<Floating>,
13 upper_bound: DVector<Floating>,
14}
15
16impl HasBounds for BroydenB {
17 fn lower_bound(&self) -> &DVector<Floating> {
18 &self.lower_bound
19 }
20 fn set_lower_bound(&mut self, lower_bound: DVector<Floating>) {
21 self.lower_bound = lower_bound;
22 }
23 fn set_upper_bound(&mut self, upper_bound: DVector<Floating>) {
24 self.upper_bound = upper_bound;
25 }
26 fn upper_bound(&self) -> &DVector<Floating> {
27 &self.upper_bound
28 }
29}
30
31impl BroydenB {
32 pub fn next_iterate_too_close(&self) -> bool {
33 match self.s_norm() {
34 Some(s) => s < &self.tol,
35 None => false,
36 }
37 }
38 pub fn gradient_next_iterate_too_close(&self) -> bool {
39 match self.y_norm() {
40 Some(y) => y < &self.tol,
41 None => false,
42 }
43 }
44 pub fn new(
45 tol: Floating,
46 x0: DVector<Floating>,
47 lower_bound: DVector<Floating>,
48 upper_bound: DVector<Floating>,
49 ) -> Self {
50 let n = x0.len();
51 let x0 = x0.box_projection(&lower_bound, &upper_bound);
52
53 let identity = DMatrix::identity(n, n);
54 BroydenB {
55 approx_inv_hessian: identity.clone(),
56 x: x0,
57 k: 0,
58 tol,
59 s_norm: None,
60 y_norm: None,
61 identity,
62 lower_bound,
63 upper_bound,
64 }
65 }
66}
67
68impl ComputeDirection for BroydenB {
69 fn compute_direction(
70 &mut self,
71 eval: &FuncEvalMultivariate,
72 ) -> Result<DVector<Floating>, SolverError> {
73 let direction = &self.x - &self.approx_inv_hessian * eval.g();
75 let direction = direction.box_projection(&self.lower_bound, &self.upper_bound);
76 let direction = direction - &self.x;
77 Ok(direction)
78 }
79}
80
81impl LineSearchSolver for BroydenB {
82 fn k(&self) -> &usize {
83 &self.k
84 }
85 fn xk(&self) -> &DVector<Floating> {
86 &self.x
87 }
88 fn xk_mut(&mut self) -> &mut DVector<Floating> {
89 &mut self.x
90 }
91 fn k_mut(&mut self) -> &mut usize {
92 &mut self.k
93 }
94 fn has_converged(&self, eval: &FuncEvalMultivariate) -> bool {
95 if self.next_iterate_too_close() {
98 warn!(target: "BroydenB","Minimization completed: next iterate too close");
99 true
100 } else if self.gradient_next_iterate_too_close() {
101 warn!(target: "BroydenB","Minimization completed: gradient next iterate too close");
102 true
103 } else {
104 eval.g().norm() < self.tol
105 }
106 }
107
108 fn update_next_iterate<LS: LineSearch>(
109 &mut self,
110 line_search: &mut LS,
111 eval_x_k: &FuncEvalMultivariate,
112 oracle: &mut impl FnMut(&DVector<Floating>) -> FuncEvalMultivariate,
113 direction: &DVector<Floating>,
114 max_iter_line_search: usize,
115 ) -> Result<(), SolverError> {
116 let step = line_search.compute_step_len(
117 self.xk(),
118 eval_x_k,
119 direction,
120 oracle,
121 max_iter_line_search,
122 );
123
124 let next_iterate = self.xk() + step * direction;
125
126 let s = &next_iterate - &self.x;
127 self.s_norm = Some(s.norm());
128 let y = oracle(&next_iterate).g() - eval_x_k.g();
129 self.y_norm = Some(y.norm());
130
131 *self.xk_mut() = next_iterate;
133
134 if self.next_iterate_too_close() {
137 return Ok(());
138 }
139
140 if self.gradient_next_iterate_too_close() {
141 return Ok(());
142 }
143
144 let hy = &self.approx_inv_hessian * &y;
146 let numerator = ((&s - hy) * s.transpose()) * &self.approx_inv_hessian;
147 let denominator = s.dot(&y);
148 self.approx_inv_hessian += numerator / denominator;
149
150 Ok(())
151 }
152}
153
154#[cfg(test)]
155mod test_broyden_b {
156 use super::*;
157 #[test]
158 fn test_outer() {
159 let a = DVector::from_vec(vec![1.0, 2.0]);
160 let b = DVector::from_vec(vec![3.0, 4.0]);
161 let c = a * b.transpose();
162 println!("{:?}", c);
163 }
164
165 #[test]
166 pub fn broyden_b_backtracking() {
167 std::env::set_var("RUST_LOG", "info");
168
169 let _ = Tracer::default()
170 .with_stdout_layer(Some(LogFormat::Normal))
171 .build();
172 let gamma = 1.;
173 let f_and_g = |x: &DVector<Floating>| -> FuncEvalMultivariate {
174 let f = 0.5 * ((x[0] + 1.).powi(2) + gamma * (x[1] - 1.).powi(2));
175 let g = DVector::from(vec![x[0] + 1., gamma * (x[1] - 1.)]);
176 (f, g).into()
177 };
178
179 let lower_bounds = DVector::from_vec(vec![-f64::INFINITY, -f64::INFINITY]);
181 let upper_oounds = DVector::from_vec(vec![f64::INFINITY, f64::INFINITY]);
182 let alpha = 1e-4;
184 let beta = 0.5;
185 let mut ls = BackTrackingB::new(alpha, beta, lower_bounds.clone(), upper_oounds.clone());
186
187 let tol = 1e-12;
189 let x_0 = DVector::from(vec![180.0, 152.0]);
190 let mut gd = BroydenB::new(tol, x_0, lower_bounds, upper_oounds);
191
192 let max_iter_solver = 1000;
194 let max_iter_line_search = 100000;
195
196 gd.minimize(
197 &mut ls,
198 f_and_g,
199 max_iter_solver,
200 max_iter_line_search,
201 None,
202 )
203 .unwrap();
204
205 println!("Iterate: {:?}", gd.xk());
206
207 let eval = f_and_g(gd.xk());
208 println!("Function eval: {:?}", eval);
209 println!("Gradient norm: {:?}", eval.g().norm());
210 println!("tol: {:?}", tol);
211
212 let convergence = gd.has_converged(&eval);
213 println!("Convergence: {:?}", convergence);
214
215 assert!((eval.f() - 0.0).abs() < 1e-6);
216 }
217}