opensrdk_optimization/
lbfgs.rs1use crate::vec::Vector as _;
2use crate::{line_search::*, Status};
3use opensrdk_linear_algebra::*;
4use std::collections::LinkedList;
5
6pub struct Lbfgs {
16 memory: usize,
17 delta: f64,
18 epsilon: f64,
19 max_iter: usize,
20 line_search: LineSearch,
21}
22
23impl Default for Lbfgs {
24 fn default() -> Self {
25 Self {
26 memory: 8,
27 delta: 1e-6,
28 epsilon: 1e-6,
29 max_iter: 0,
30 line_search: LineSearch::default(),
31 }
32 }
33}
34
35impl Lbfgs {
36 pub fn new(
37 memory: usize,
38 delta: f64,
39 epsilon: f64,
40 max_iter: usize,
41 line_search: LineSearch,
42 ) -> Self {
43 Self {
44 memory,
45 delta,
46 epsilon,
47 max_iter,
48 line_search,
49 }
50 }
51
52 pub fn with_memory(mut self, memory: usize) -> Self {
53 self.memory = memory;
54
55 self
56 }
57
58 pub fn with_delta(mut self, delta: f64) -> Self {
59 assert!(delta.is_sign_positive(), "delta must be positive");
60
61 self.delta = delta;
62
63 self
64 }
65
66 pub fn with_epsilon(mut self, epsilon: f64) -> Self {
67 assert!(epsilon.is_sign_positive(), "epsilon must be positive");
68
69 self.epsilon = epsilon;
70
71 self
72 }
73
74 pub fn with_max_iter(mut self, max_iter: usize) -> Self {
75 self.max_iter = max_iter;
76
77 self
78 }
79
80 pub fn with_line_search(mut self, line_search: LineSearch) -> Self {
81 self.line_search = line_search;
82
83 self
84 }
85
86 pub fn minimize(&self, x: &mut [f64], fx_gfx: &dyn Fn(&[f64]) -> (f64, Vec<f64>)) -> Status {
87 let mut x_prev = Matrix::new(x.len(), 1);
88 let fx_prev = 0.0;
89 let mut gfx_prev = Matrix::new(x.len(), 1);
90
91 let mut s_y_rho_inv = LinkedList::<(Matrix, Matrix, f64)>::new();
92
93 for k in 1.. {
94 if self.max_iter != 0 && self.max_iter <= k {
95 return Status::MaxIter;
96 }
97
98 let (fx, gfx) = fx_gfx(x);
99 let dfx = fx - fx_prev;
100
101 if dfx.abs() / fx < self.delta {
102 return Status::Delta;
103 }
104
105 if gfx.l2_norm() < self.epsilon {
106 return Status::Epsilon;
107 }
108
109 let sk = x.to_vec().col_mat() - x_prev;
110 let yk = gfx.to_vec().col_mat() - gfx_prev;
111 let rhok_inv = (yk.t() * &sk)[0][0];
112
113 let mut h = gfx.clone().col_mat();
114
115 let mut alpha = vec![0.0; k as usize];
116
117 for (i, (si, yi, rhoi_inv)) in s_y_rho_inv.iter().enumerate().rev() {
118 alpha[i] = (si.t() * &h)[0][0] / rhoi_inv;
119
120 let alphai = alpha[i];
121
122 h = h - alphai * yi.clone();
123 }
124
125 let gamma = (sk.t() * &yk)[0][0] / (yk.t() * &yk)[0][0];
126
127 let mut z: Matrix = gamma * h.clone();
128
129 for (i, (si, yi, rhoi_inv)) in s_y_rho_inv.iter().enumerate() {
130 let alphai = alpha[i];
131 let betai = (yi.t() * &z)[0][0] / rhoi_inv;
132
133 z = z + (alphai - betai) * si.clone();
134 }
135
136 z = -1.0 * z;
137
138 s_y_rho_inv.push_back((sk, yk, rhok_inv));
139 if s_y_rho_inv.len() > self.memory {
140 s_y_rho_inv.pop_front();
141 }
142
143 let step_size = self.line_search.search(fx_gfx, x, z.slice());
144 let dx = step_size * z;
145
146 if !dx.slice().l2_norm().is_finite() {
147 return Status::NaN;
148 }
149
150 gfx_prev = h;
151 x_prev = x.to_vec().col_mat();
152 x.clone_from_slice((&x_prev + dx).slice());
153 }
154
155 Status::Success
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use crate::{lbfgs::*, numerical_diff};
162 use std::{error::Error, f64::consts::E, f64::consts::PI};
163 #[test]
164 fn it_works() {
165 result().unwrap();
166 }
167
168 #[test]
169 fn result() -> Result<(), Box<dyn Error>> {
170 let mut x = vec![1.2, 3.456789];
171
172 let fx_gfx = |x: &[f64]| {
173 let fx = 1.0 + 2.0 * x[0].powi(2) + 3.0 * x[1].powi(4);
174 let gx = vec![4.0 * x[0], 12.0 * x[1].powi(3)];
175
176 (fx, gx)
177 };
178 Lbfgs::default().minimize(&mut x, &fx_gfx);
179
180 println!("x: {:#?}", x);
181
182 Ok(())
183 }
184
185 #[test]
186 fn result_() -> Result<(), Box<dyn Error>> {
187 let mut x = vec![30.0, -20.0];
188
189 let fx = |x: &[f64]| {
190 let fx = 20.0
191 - 20.0
192 * (-0.2
193 * (1.0 / x.len() as f64 * x.into_iter().map(|xi| xi.powi(2)).sum::<f64>())
194 .sqrt())
195 .exp()
196 + E
197 - (1.0 / x.len() as f64
198 * x.into_iter().map(|xi| (2.0 * PI * xi).cos()).sum::<f64>())
199 .exp();
200
201 fx
202 };
203 let fx_gfx = |x: &[f64]| {
204 let gfx = numerical_diff(&fx, x, 1e-3);
205 println!("{:#?}", gfx);
206
207 (fx(x), gfx)
208 };
209 Lbfgs::default().minimize(&mut x, &fx_gfx);
210
211 println!("x: {:#?}", x);
212
213 Ok(())
214 }
215}