scirs2_optimize/constrained/
slsqp.rs1use crate::constrained::{Constraint, ConstraintFn, ConstraintKind, Options};
4use crate::error::{OptimizeError, OptimizeResult};
5use crate::result::OptimizeResults;
6use ndarray::{Array1, Array2, ArrayBase, Axis, Data, Ix1};
7
8#[allow(clippy::many_single_char_names)]
10pub fn minimize_slsqp<F, S>(
11 func: F,
12 x0: &ArrayBase<S, Ix1>,
13 constraints: &[Constraint<ConstraintFn>],
14 options: &Options,
15) -> OptimizeResult<OptimizeResults<f64>>
16where
17 F: Fn(&[f64]) -> f64,
18 S: Data<Elem = f64>,
19{
20 let ftol = options.ftol.unwrap_or(1e-8);
22 let gtol = options.gtol.unwrap_or(1e-8);
23 let ctol = options.ctol.unwrap_or(1e-8);
24 let maxiter = options.maxiter.unwrap_or(100 * x0.len());
25 let eps = options.eps.unwrap_or(1e-8);
26
27 let n = x0.len();
29 let mut x = x0.to_owned();
30 let mut f = func(x.as_slice().unwrap());
31 let mut nfev = 1;
32
33 let mut lambda = Array1::zeros(constraints.len());
35
36 let mut g = Array1::zeros(n);
38 for i in 0..n {
39 let mut x_h = x.clone();
40 x_h[i] += eps;
41 let f_h = func(x_h.as_slice().unwrap());
42 g[i] = (f_h - f) / eps;
43 nfev += 1;
44 }
45
46 let mut c = Array1::zeros(constraints.len());
48 let _ceq: Array1<f64> = Array1::zeros(0); for (i, constraint) in constraints.iter().enumerate() {
50 if !constraint.is_bounds() {
51 let val = (constraint.fun)(x.as_slice().unwrap());
52
53 match constraint.kind {
54 ConstraintKind::Inequality => {
55 c[i] = val; }
57 ConstraintKind::Equality => {
58 return Err(OptimizeError::NotImplementedError(
60 "Equality constraints not fully implemented in SLSQP yet".to_string(),
61 ));
62 }
63 }
64 }
65 }
66
67 let mut a = Array2::zeros((constraints.len(), n));
69 for (i, constraint) in constraints.iter().enumerate() {
70 if !constraint.is_bounds() {
71 for j in 0..n {
72 let mut x_h = x.clone();
73 x_h[j] += eps;
74 let c_h = (constraint.fun)(x_h.as_slice().unwrap());
75 a[[i, j]] = (c_h - c[i]) / eps;
76 nfev += 1;
77 }
78 }
79 }
80
81 let mut h_inv = Array2::eye(n); let mut iter = 0;
86
87 while iter < maxiter {
88 let mut max_constraint_violation = 0.0;
90 for (i, &ci) in c.iter().enumerate() {
91 if constraints[i].kind == ConstraintKind::Inequality && ci < -ctol {
92 max_constraint_violation = f64::max(max_constraint_violation, -ci);
93 }
94 }
95
96 if g.iter().all(|&gi| gi.abs() < gtol) && max_constraint_violation < ctol {
98 break;
99 }
100
101 let mut p = Array1::zeros(n);
104
105 if max_constraint_violation > ctol {
106 for (i, &ci) in c.iter().enumerate() {
108 if ci < -ctol {
109 for j in 0..n {
111 p[j] -= a[[i, j]] * ci; }
113 }
114 }
115 } else {
116 p = -&h_inv.dot(&g);
118
119 for (i, &ci) in c.iter().enumerate() {
121 if ci.abs() < ctol {
122 let mut normal = Array1::zeros(n);
124 for j in 0..n {
125 normal[j] = a[[i, j]];
126 }
127 let norm = normal.dot(&normal).sqrt();
128 if norm > 1e-10 {
129 normal = &normal / norm;
130 let p_dot_normal = p.dot(&normal);
131
132 if p_dot_normal < 0.0 {
134 p = &p - &(&normal * p_dot_normal);
135 }
136 }
137 }
138 }
139 }
140
141 let mut alpha = 1.0;
143 let c1 = 1e-4; let rho = 0.5; let mut x_new = &x + &(&p * alpha);
148 let mut f_new = func(x_new.as_slice().unwrap());
149 nfev += 1;
150
151 let mut c_new = Array1::zeros(constraints.len());
153 for (i, constraint) in constraints.iter().enumerate() {
154 if !constraint.is_bounds() {
155 c_new[i] = (constraint.fun)(x_new.as_slice().unwrap());
156 nfev += 1;
157 }
158 }
159
160 let mut max_viol = 0.0;
162 let mut max_viol_new = 0.0;
163
164 for (i, constraint) in constraints.iter().enumerate() {
165 if constraint.kind == ConstraintKind::Inequality {
166 max_viol = f64::max(max_viol, f64::max(0.0, -c[i]));
167 max_viol_new = f64::max(max_viol_new, f64::max(0.0, -c_new[i]));
168 }
169 }
170
171 let g_dot_p = g.dot(&p);
173
174 while (f_new > f + c1 * alpha * g_dot_p && max_viol <= ctol)
176 || (max_viol_new > max_viol && max_viol > ctol)
177 {
178 alpha *= rho;
179
180 if alpha < 1e-10 {
182 break;
183 }
184
185 x_new = &x + &(&p * alpha);
186 f_new = func(x_new.as_slice().unwrap());
187 nfev += 1;
188
189 for (i, constraint) in constraints.iter().enumerate() {
191 if !constraint.is_bounds() {
192 c_new[i] = (constraint.fun)(x_new.as_slice().unwrap());
193 nfev += 1;
194 }
195 }
196
197 max_viol_new = 0.0;
198 for (i, constraint) in constraints.iter().enumerate() {
199 if constraint.kind == ConstraintKind::Inequality {
200 max_viol_new = f64::max(max_viol_new, f64::max(0.0, -c_new[i]));
201 }
202 }
203 }
204
205 if ((f - f_new).abs() < ftol * (1.0 + f.abs())) && alpha * p.dot(&p).sqrt() < ftol {
207 break;
208 }
209
210 let mut g_new = Array1::zeros(n);
212 for i in 0..n {
213 let mut x_h = x_new.clone();
214 x_h[i] += eps;
215 let f_h = func(x_h.as_slice().unwrap());
216 g_new[i] = (f_h - f_new) / eps;
217 nfev += 1;
218 }
219
220 let mut a_new = Array2::zeros((constraints.len(), n));
222 for (i, constraint) in constraints.iter().enumerate() {
223 if !constraint.is_bounds() {
224 for j in 0..n {
225 let mut x_h = x_new.clone();
226 x_h[j] += eps;
227 let c_h = (constraint.fun)(x_h.as_slice().unwrap());
228 a_new[[i, j]] = (c_h - c_new[i]) / eps;
229 nfev += 1;
230 }
231 }
232 }
233
234 for (i, constraint) in constraints.iter().enumerate() {
236 if constraint.kind == ConstraintKind::Inequality && c_new[i].abs() < ctol {
237 let mut normal = Array1::zeros(n);
239 for j in 0..n {
240 normal[j] = a_new[[i, j]];
241 }
242
243 let norm = normal.dot(&normal).sqrt();
244 if norm > 1e-10 {
245 normal = &normal / norm;
246 lambda[i] = -g_new.dot(&normal);
247 }
248 } else {
249 lambda[i] = 0.0;
250 }
251 }
252
253 let s = &x_new - &x;
255 let y = &g_new - &g;
256
257 let mut y_lag = y.clone();
259 for (i, &li) in lambda.iter().enumerate() {
260 if li > 0.0 {
261 for j in 0..n {
263 y_lag[j] += li * (a_new[[i, j]] - a[[i, j]]);
266 }
267 }
268 }
269
270 let rho_bfgs = 1.0 / y_lag.dot(&s);
272 if rho_bfgs.is_finite() && rho_bfgs > 0.0 {
273 let i_mat = Array2::eye(n);
274 let y_row = y_lag.clone().insert_axis(Axis(0));
275 let s_col = s.clone().insert_axis(Axis(1));
276 let y_s_t = y_row.dot(&s_col);
277
278 let term1 = &i_mat - &(&y_s_t * rho_bfgs);
279 let s_row = s.clone().insert_axis(Axis(0));
280 let y_col = y_lag.clone().insert_axis(Axis(1));
281 let s_y_t = s_row.dot(&y_col);
282
283 let term2 = &i_mat - &(&s_y_t * rho_bfgs);
284 let term3 = &term1.dot(&h_inv);
285 h_inv = term3.dot(&term2) + rho_bfgs * s_col.dot(&s_row);
286 }
287
288 x = x_new;
290 f = f_new;
291 g = g_new;
292 c = c_new;
293 a = a_new;
294
295 iter += 1;
296 }
297
298 let mut c_result = Array1::zeros(constraints.len());
300 for (i, constraint) in constraints.iter().enumerate() {
301 if !constraint.is_bounds() {
302 c_result[i] = c[i];
303 }
304 }
305
306 let mut result = OptimizeResults::default();
308 result.x = x;
309 result.fun = f;
310 result.jac = Some(g.into_raw_vec_and_offset().0);
311 result.constr = Some(c_result);
312 result.nfev = nfev;
313 result.nit = iter;
314 result.success = iter < maxiter;
315
316 if result.success {
317 result.message = "Optimization terminated successfully.".to_string();
318 } else {
319 result.message = "Maximum iterations reached.".to_string();
320 }
321
322 Ok(result)
323}