optimization_solvers/quasi_newton/
dfp_b.rs

1use 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        // Ok(-&self.approx_inv_hessian * eval.g())
73        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        // either the gradient is small or the difference between the iterates is small
95        // eval.g().norm() < self.tol || self.next_iterate_too_close()
96        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        //updating iterate here, and then we will update the inverse hessian (if corrections are not too small)
131        *self.xk_mut() = next_iterate;
132
133        // We update the inverse hessian and the corrections in this hook which is triggered just after the calculation of the next iterate
134
135        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        // DFPB update
144        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        //bounds p
181        let lower_bounds = DVector::from_vec(vec![-f64::INFINITY, -f64::INFINITY]);
182        let upper_oounds = DVector::from_vec(vec![f64::INFINITY, f64::INFINITY]);
183        // Linesearch builder
184        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        // Gradient descent builder
189        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        // Minimization
194        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}