optimization_solvers/quasi_newton/
dfp_b.rs1use super::*;
2
3#[derive(derive_getters::Getters)]
4pub struct DFPB {
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 DFPB {
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 DFPB {
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 let identity = DMatrix::identity(n, n);
53 DFPB {
54 approx_inv_hessian: identity.clone(),
55 x: x0,
56 k: 0,
57 tol,
58 s_norm: None,
59 y_norm: None,
60 identity,
61 lower_bound,
62 upper_bound,
63 }
64 }
65}
66
67impl ComputeDirection for DFPB {
68 fn compute_direction(
69 &mut self,
70 eval: &FuncEvalMultivariate,
71 ) -> Result<DVector<Floating>, SolverError> {
72 let direction = &self.x - &self.approx_inv_hessian * eval.g();
74 let direction = direction.box_projection(&self.lower_bound, &self.upper_bound);
75 let direction = direction - &self.x;
76 Ok(direction)
77 }
78}
79
80impl LineSearchSolver for DFPB {
81 fn k(&self) -> &usize {
82 &self.k
83 }
84 fn xk(&self) -> &DVector<Floating> {
85 &self.x
86 }
87 fn xk_mut(&mut self) -> &mut DVector<Floating> {
88 &mut self.x
89 }
90 fn k_mut(&mut self) -> &mut usize {
91 &mut self.k
92 }
93 fn has_converged(&self, eval: &FuncEvalMultivariate) -> bool {
94 if self.next_iterate_too_close() {
97 warn!(target: "DFPB","Minimization completed: next iterate too close");
98 true
99 } else if self.gradient_next_iterate_too_close() {
100 warn!(target: "DFPB","Minimization completed: gradient next iterate too close");
101 true
102 } else {
103 eval.g().norm() < self.tol
104 }
105 }
106
107 fn update_next_iterate<LS: LineSearch>(
108 &mut self,
109 line_search: &mut LS,
110 eval_x_k: &FuncEvalMultivariate,
111 oracle: &mut impl FnMut(&DVector<Floating>) -> FuncEvalMultivariate,
112 direction: &DVector<Floating>,
113 max_iter_line_search: usize,
114 ) -> Result<(), SolverError> {
115 let step = line_search.compute_step_len(
116 self.xk(),
117 eval_x_k,
118 direction,
119 oracle,
120 max_iter_line_search,
121 );
122
123 let next_iterate = self.xk() + step * direction;
124
125 let s = &next_iterate - &self.x;
126 self.s_norm = Some(s.norm());
127 let y = oracle(&next_iterate).g() - eval_x_k.g();
128 self.y_norm = Some(y.norm());
129
130 *self.xk_mut() = next_iterate;
132
133 if self.next_iterate_too_close() {
136 return Ok(());
137 }
138
139 if self.gradient_next_iterate_too_close() {
140 return Ok(());
141 }
142
143 let ss = &s * s.transpose();
145 let yy = &y * y.transpose();
146 let sy = s.dot(&y);
147 let yhy = y.dot(&(&self.approx_inv_hessian * &y));
148 self.approx_inv_hessian +=
149 ss / sy - (&self.approx_inv_hessian * &yy * &self.approx_inv_hessian) / yhy;
150
151 Ok(())
152 }
153}
154
155#[cfg(test)]
156mod test_dfpb {
157 use super::*;
158 #[test]
159 fn test_outer() {
160 let a = DVector::from_vec(vec![1.0, 2.0]);
161 let b = DVector::from_vec(vec![3.0, 4.0]);
162 let c = a * b.transpose();
163 println!("{:?}", c);
164 }
165
166 #[test]
167 pub fn dfpb_backtracking() {
168 std::env::set_var("RUST_LOG", "info");
169
170 let _ = Tracer::default()
171 .with_stdout_layer(Some(LogFormat::Normal))
172 .build();
173 let gamma = 1.;
174 let f_and_g = |x: &DVector<Floating>| -> FuncEvalMultivariate {
175 let f = 0.5 * ((x[0] + 1.).powi(2) + gamma * (x[1] - 1.).powi(2));
176 let g = DVector::from(vec![x[0] + 1., gamma * (x[1] - 1.)]);
177 (f, g).into()
178 };
179
180 let lower_bounds = DVector::from_vec(vec![-f64::INFINITY, -f64::INFINITY]);
182 let upper_oounds = DVector::from_vec(vec![f64::INFINITY, f64::INFINITY]);
183 let alpha = 1e-4;
185 let beta = 0.5;
186 let mut ls = BackTrackingB::new(alpha, beta, lower_bounds.clone(), upper_oounds.clone());
187
188 let tol = 1e-12;
190 let x_0 = DVector::from(vec![180.0, 152.0]);
191 let mut gd = DFPB::new(tol, x_0, lower_bounds, upper_oounds);
192
193 let max_iter_solver = 1000;
195 let max_iter_line_search = 100000;
196
197 gd.minimize(
198 &mut ls,
199 f_and_g,
200 max_iter_solver,
201 max_iter_line_search,
202 None,
203 )
204 .unwrap();
205
206 println!("Iterate: {:?}", gd.xk());
207
208 let eval = f_and_g(gd.xk());
209 println!("Function eval: {:?}", eval);
210 println!("Gradient norm: {:?}", eval.g().norm());
211 println!("tol: {:?}", tol);
212
213 let convergence = gd.has_converged(&eval);
214 println!("Convergence: {:?}", convergence);
215
216 assert!((eval.f() - 0.0).abs() < 1e-6);
217 }
218}