1use crate::error::{OptimizeError, OptimizeResult};
41use crate::result::OptimizeResults;
42use ndarray::{Array1, Array2, ArrayBase, Axis, Data, Ix1};
43use std::fmt;
44
45pub type ConstraintFn = fn(&[f64]) -> f64;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Method {
51 SLSQP,
53
54 TrustConstr,
56
57 COBYLA,
59}
60
61impl fmt::Display for Method {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 match self {
64 Method::SLSQP => write!(f, "SLSQP"),
65 Method::TrustConstr => write!(f, "trust-constr"),
66 Method::COBYLA => write!(f, "COBYLA"),
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
73pub struct Options {
74 pub maxiter: Option<usize>,
76
77 pub ftol: Option<f64>,
79
80 pub gtol: Option<f64>,
82
83 pub ctol: Option<f64>,
85
86 pub eps: Option<f64>,
88
89 pub disp: bool,
91
92 pub return_all: bool,
94}
95
96impl Default for Options {
97 fn default() -> Self {
98 Options {
99 maxiter: None,
100 ftol: Some(1e-8),
101 gtol: Some(1e-8),
102 ctol: Some(1e-8),
103 eps: Some(1e-8),
104 disp: false,
105 return_all: false,
106 }
107 }
108}
109
110pub struct Constraint<F> {
112 pub fun: F,
114
115 pub kind: ConstraintKind,
117
118 pub lb: Option<f64>,
120
121 pub ub: Option<f64>,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum ConstraintKind {
128 Equality,
130
131 Inequality,
133}
134
135impl Constraint<fn(&[f64]) -> f64> {
136 pub const EQUALITY: ConstraintKind = ConstraintKind::Equality;
138
139 pub const INEQUALITY: ConstraintKind = ConstraintKind::Inequality;
141
142 pub fn new(fun: fn(&[f64]) -> f64, kind: ConstraintKind) -> Self {
144 Constraint {
145 fun,
146 kind,
147 lb: None,
148 ub: None,
149 }
150 }
151
152 pub fn new_bounds(lb: Option<f64>, ub: Option<f64>) -> Self {
154 Constraint {
155 fun: |_| 0.0, kind: ConstraintKind::Inequality,
157 lb,
158 ub,
159 }
160 }
161}
162
163impl<F> Constraint<F> {
164 pub fn is_bounds(&self) -> bool {
166 self.lb.is_some() || self.ub.is_some()
167 }
168}
169
170pub fn minimize_constrained<F, S>(
215 func: F,
216 x0: &ArrayBase<S, Ix1>,
217 constraints: &[Constraint<ConstraintFn>],
218 method: Method,
219 options: Option<Options>,
220) -> OptimizeResult<OptimizeResults<f64>>
221where
222 F: Fn(&[f64]) -> f64,
223 S: Data<Elem = f64>,
224{
225 let options = options.unwrap_or_default();
226
227 match method {
229 Method::SLSQP => minimize_slsqp(func, x0, constraints, &options),
230 Method::TrustConstr => minimize_trust_constr(func, x0, constraints, &options),
231 _ => Err(OptimizeError::NotImplementedError(format!(
232 "Method {:?} is not yet implemented",
233 method
234 ))),
235 }
236}
237
238fn minimize_slsqp<F, S>(
240 func: F,
241 x0: &ArrayBase<S, Ix1>,
242 constraints: &[Constraint<ConstraintFn>],
243 options: &Options,
244) -> OptimizeResult<OptimizeResults<f64>>
245where
246 F: Fn(&[f64]) -> f64,
247 S: Data<Elem = f64>,
248{
249 let ftol = options.ftol.unwrap_or(1e-8);
251 let gtol = options.gtol.unwrap_or(1e-8);
252 let ctol = options.ctol.unwrap_or(1e-8);
253 let maxiter = options.maxiter.unwrap_or(100 * x0.len());
254 let eps = options.eps.unwrap_or(1e-8);
255
256 let n = x0.len();
258 let mut x = x0.to_owned();
259 let mut f = func(x.as_slice().unwrap());
260 let mut nfev = 1;
261
262 let mut lambda = Array1::zeros(constraints.len());
264
265 let mut g = Array1::zeros(n);
267 for i in 0..n {
268 let mut x_h = x.clone();
269 x_h[i] += eps;
270 let f_h = func(x_h.as_slice().unwrap());
271 g[i] = (f_h - f) / eps;
272 nfev += 1;
273 }
274
275 let mut c = Array1::zeros(constraints.len());
277 let _ceq: Array1<f64> = Array1::zeros(0); for (i, constraint) in constraints.iter().enumerate() {
279 if !constraint.is_bounds() {
280 let val = (constraint.fun)(x.as_slice().unwrap());
281
282 match constraint.kind {
283 ConstraintKind::Inequality => {
284 c[i] = val; }
286 ConstraintKind::Equality => {
287 return Err(OptimizeError::NotImplementedError(
289 "Equality constraints not fully implemented in SLSQP yet".to_string(),
290 ));
291 }
292 }
293 }
294 }
295
296 let mut a = Array2::zeros((constraints.len(), n));
298 for (i, constraint) in constraints.iter().enumerate() {
299 if !constraint.is_bounds() {
300 for j in 0..n {
301 let mut x_h = x.clone();
302 x_h[j] += eps;
303 let c_h = (constraint.fun)(x_h.as_slice().unwrap());
304 a[[i, j]] = (c_h - c[i]) / eps;
305 nfev += 1;
306 }
307 }
308 }
309
310 let mut h_inv = Array2::eye(n); let mut iter = 0;
315
316 while iter < maxiter {
317 let mut max_constraint_violation = 0.0;
319 for (i, &ci) in c.iter().enumerate() {
320 if constraints[i].kind == ConstraintKind::Inequality && ci < -ctol {
321 max_constraint_violation = f64::max(max_constraint_violation, -ci);
322 }
323 }
324
325 if g.iter().all(|&gi| gi.abs() < gtol) && max_constraint_violation < ctol {
327 break;
328 }
329
330 let mut p = Array1::zeros(n);
333
334 if max_constraint_violation > ctol {
335 for (i, &ci) in c.iter().enumerate() {
337 if ci < -ctol {
338 for j in 0..n {
340 p[j] -= a[[i, j]] * ci; }
342 }
343 }
344 } else {
345 p = -&h_inv.dot(&g);
347
348 for (i, &ci) in c.iter().enumerate() {
350 if ci.abs() < ctol {
351 let mut normal = Array1::zeros(n);
353 for j in 0..n {
354 normal[j] = a[[i, j]];
355 }
356 let norm = normal.dot(&normal).sqrt();
357 if norm > 1e-10 {
358 normal = &normal / norm;
359 let p_dot_normal = p.dot(&normal);
360
361 if p_dot_normal < 0.0 {
363 p = &p - &(&normal * p_dot_normal);
364 }
365 }
366 }
367 }
368 }
369
370 let mut alpha = 1.0;
372 let c1 = 1e-4; let rho = 0.5; let mut x_new = &x + &(&p * alpha);
377 let mut f_new = func(x_new.as_slice().unwrap());
378 nfev += 1;
379
380 let mut c_new = Array1::zeros(constraints.len());
382 for (i, constraint) in constraints.iter().enumerate() {
383 if !constraint.is_bounds() {
384 c_new[i] = (constraint.fun)(x_new.as_slice().unwrap());
385 nfev += 1;
386 }
387 }
388
389 let mut max_viol = 0.0;
391 let mut max_viol_new = 0.0;
392
393 for (i, constraint) in constraints.iter().enumerate() {
394 if constraint.kind == ConstraintKind::Inequality {
395 max_viol = f64::max(max_viol, f64::max(0.0, -c[i]));
396 max_viol_new = f64::max(max_viol_new, f64::max(0.0, -c_new[i]));
397 }
398 }
399
400 let g_dot_p = g.dot(&p);
402
403 while (f_new > f + c1 * alpha * g_dot_p && max_viol <= ctol)
405 || (max_viol_new > max_viol && max_viol > ctol)
406 {
407 alpha *= rho;
408
409 if alpha < 1e-10 {
411 break;
412 }
413
414 x_new = &x + &(&p * alpha);
415 f_new = func(x_new.as_slice().unwrap());
416 nfev += 1;
417
418 for (i, constraint) in constraints.iter().enumerate() {
420 if !constraint.is_bounds() {
421 c_new[i] = (constraint.fun)(x_new.as_slice().unwrap());
422 nfev += 1;
423 }
424 }
425
426 max_viol_new = 0.0;
427 for (i, constraint) in constraints.iter().enumerate() {
428 if constraint.kind == ConstraintKind::Inequality {
429 max_viol_new = f64::max(max_viol_new, f64::max(0.0, -c_new[i]));
430 }
431 }
432 }
433
434 if ((f - f_new).abs() < ftol * (1.0 + f.abs())) && alpha * p.dot(&p).sqrt() < ftol {
436 break;
437 }
438
439 let mut g_new = Array1::zeros(n);
441 for i in 0..n {
442 let mut x_h = x_new.clone();
443 x_h[i] += eps;
444 let f_h = func(x_h.as_slice().unwrap());
445 g_new[i] = (f_h - f_new) / eps;
446 nfev += 1;
447 }
448
449 let mut a_new = Array2::zeros((constraints.len(), n));
451 for (i, constraint) in constraints.iter().enumerate() {
452 if !constraint.is_bounds() {
453 for j in 0..n {
454 let mut x_h = x_new.clone();
455 x_h[j] += eps;
456 let c_h = (constraint.fun)(x_h.as_slice().unwrap());
457 a_new[[i, j]] = (c_h - c_new[i]) / eps;
458 nfev += 1;
459 }
460 }
461 }
462
463 for (i, constraint) in constraints.iter().enumerate() {
465 if constraint.kind == ConstraintKind::Inequality && c_new[i].abs() < ctol {
466 let mut normal = Array1::zeros(n);
468 for j in 0..n {
469 normal[j] = a_new[[i, j]];
470 }
471
472 let norm = normal.dot(&normal).sqrt();
473 if norm > 1e-10 {
474 normal = &normal / norm;
475 lambda[i] = -g_new.dot(&normal);
476 }
477 } else {
478 lambda[i] = 0.0;
479 }
480 }
481
482 let s = &x_new - &x;
484 let y = &g_new - &g;
485
486 let mut y_lag = y.clone();
488 for (i, &li) in lambda.iter().enumerate() {
489 if li > 0.0 {
490 for j in 0..n {
492 y_lag[j] += li * (a_new[[i, j]] - a[[i, j]]);
495 }
496 }
497 }
498
499 let rho_bfgs = 1.0 / y_lag.dot(&s);
501 if rho_bfgs.is_finite() && rho_bfgs > 0.0 {
502 let i_mat = Array2::eye(n);
503 let y_row = y_lag.clone().insert_axis(Axis(0));
504 let s_col = s.clone().insert_axis(Axis(1));
505 let y_s_t = y_row.dot(&s_col);
506
507 let term1 = &i_mat - &(&y_s_t * rho_bfgs);
508 let s_row = s.clone().insert_axis(Axis(0));
509 let y_col = y_lag.clone().insert_axis(Axis(1));
510 let s_y_t = s_row.dot(&y_col);
511
512 let term2 = &i_mat - &(&s_y_t * rho_bfgs);
513 let term3 = &term1.dot(&h_inv);
514 h_inv = term3.dot(&term2) + rho_bfgs * s_col.dot(&s_row);
515 }
516
517 x = x_new;
519 f = f_new;
520 g = g_new;
521 c = c_new;
522 a = a_new;
523
524 iter += 1;
525 }
526
527 let mut c_result = Array1::zeros(constraints.len());
529 for (i, constraint) in constraints.iter().enumerate() {
530 if !constraint.is_bounds() {
531 c_result[i] = c[i];
532 }
533 }
534
535 let mut result = OptimizeResults::default();
537 result.x = x;
538 result.fun = f;
539 result.jac = Some(g.into_raw_vec_and_offset().0);
540 result.constr = Some(c_result);
541 result.nfev = nfev;
542 result.nit = iter;
543 result.success = iter < maxiter;
544
545 if result.success {
546 result.message = "Optimization terminated successfully.".to_string();
547 } else {
548 result.message = "Maximum iterations reached.".to_string();
549 }
550
551 Ok(result)
552}
553
554fn minimize_trust_constr<F, S>(
556 func: F,
557 x0: &ArrayBase<S, Ix1>,
558 constraints: &[Constraint<ConstraintFn>],
559 options: &Options,
560) -> OptimizeResult<OptimizeResults<f64>>
561where
562 F: Fn(&[f64]) -> f64,
563 S: Data<Elem = f64>,
564{
565 let ftol = options.ftol.unwrap_or(1e-8);
567 let gtol = options.gtol.unwrap_or(1e-8);
568 let ctol = options.ctol.unwrap_or(1e-8);
569 let maxiter = options.maxiter.unwrap_or(100 * x0.len());
570 let eps = options.eps.unwrap_or(1e-8);
571
572 let n = x0.len();
574 let mut x = x0.to_owned();
575 let mut f = func(x.as_slice().unwrap());
576 let mut nfev = 1;
577
578 let mut lambda = Array1::zeros(constraints.len());
580
581 let mut g = Array1::zeros(n);
583 for i in 0..n {
584 let mut x_h = x.clone();
585 x_h[i] += eps;
586 let f_h = func(x_h.as_slice().unwrap());
587 g[i] = (f_h - f) / eps;
588 nfev += 1;
589 }
590
591 let mut c = Array1::zeros(constraints.len());
593 for (i, constraint) in constraints.iter().enumerate() {
594 if !constraint.is_bounds() {
595 let val = (constraint.fun)(x.as_slice().unwrap());
596
597 match constraint.kind {
598 ConstraintKind::Inequality => {
599 c[i] = val; }
601 ConstraintKind::Equality => {
602 return Err(OptimizeError::NotImplementedError(
604 "Equality constraints not fully implemented in Trust-Region yet"
605 .to_string(),
606 ));
607 }
608 }
609 }
610 }
611
612 let mut a = Array2::zeros((constraints.len(), n));
614 for (i, constraint) in constraints.iter().enumerate() {
615 if !constraint.is_bounds() {
616 for j in 0..n {
617 let mut x_h = x.clone();
618 x_h[j] += eps;
619 let c_h = (constraint.fun)(x_h.as_slice().unwrap());
620 a[[i, j]] = (c_h - c[i]) / eps;
621 nfev += 1;
622 }
623 }
624 }
625
626 let mut delta = 1.0;
628
629 let mut b = Array2::eye(n);
631
632 let mut iter = 0;
634
635 while iter < maxiter {
636 let mut max_constraint_violation = 0.0;
638 for (i, &ci) in c.iter().enumerate() {
639 if constraints[i].kind == ConstraintKind::Inequality && ci < -ctol {
640 max_constraint_violation = f64::max(max_constraint_violation, -ci);
641 }
642 }
643
644 if g.iter().all(|&gi| gi.abs() < gtol) && max_constraint_violation < ctol {
646 break;
647 }
648
649 let mut lag_grad = g.clone();
651 for (i, &li) in lambda.iter().enumerate() {
652 if li > 0.0 || c[i] < ctol {
653 for j in 0..n {
655 lag_grad[j] -= li * a[[i, j]];
658 }
659 }
660 }
661
662 let (p, predicted_reduction) =
667 compute_trust_region_step_constrained(&lag_grad, &b, &a, &c, delta, constraints, ctol);
668
669 let x_new = &x + &p;
671
672 let f_new = func(x_new.as_slice().unwrap());
674 nfev += 1;
675
676 let mut c_new = Array1::zeros(constraints.len());
677 for (i, constraint) in constraints.iter().enumerate() {
678 if !constraint.is_bounds() {
679 c_new[i] = (constraint.fun)(x_new.as_slice().unwrap());
680 nfev += 1;
681 }
682 }
683
684 let mut merit = f;
686 let mut merit_new = f_new;
687
688 let penalty = 10.0; for (i, &ci) in c.iter().enumerate() {
691 if constraints[i].kind == ConstraintKind::Inequality {
692 merit += penalty * f64::max(0.0, -ci);
693 merit_new += penalty * f64::max(0.0, -c_new[i]);
694 }
695 }
696
697 let actual_reduction = merit - merit_new;
699
700 let rho = if predicted_reduction > 0.0 {
702 actual_reduction / predicted_reduction
703 } else {
704 0.0
705 };
706
707 if rho < 0.25 {
709 delta *= 0.5;
710 } else if rho > 0.75 && p.iter().map(|&pi| pi * pi).sum::<f64>().sqrt() >= 0.9 * delta {
711 delta *= 2.0;
712 }
713
714 if rho > 0.1 {
716 x = x_new;
718 f = f_new;
719 c = c_new;
720
721 if (merit - merit_new).abs() < ftol * (1.0 + merit.abs()) {
723 break;
724 }
725
726 let mut g_new = Array1::zeros(n);
728 for i in 0..n {
729 let mut x_h = x.clone();
730 x_h[i] += eps;
731 let f_h = func(x_h.as_slice().unwrap());
732 g_new[i] = (f_h - f) / eps;
733 nfev += 1;
734 }
735
736 let mut a_new = Array2::zeros((constraints.len(), n));
738 for (i, constraint) in constraints.iter().enumerate() {
739 if !constraint.is_bounds() {
740 for j in 0..n {
741 let mut x_h = x.clone();
742 x_h[j] += eps;
743 let c_h = (constraint.fun)(x_h.as_slice().unwrap());
744 a_new[[i, j]] = (c_h - c[i]) / eps;
745 nfev += 1;
746 }
747 }
748 }
749
750 for (i, constraint) in constraints.iter().enumerate() {
752 if constraint.kind == ConstraintKind::Inequality {
753 if c[i] < ctol {
754 lambda[i] = f64::max(0.0, lambda[i] - c[i] * penalty);
757 } else {
758 lambda[i] = f64::max(0.0, lambda[i] - 0.1 * lambda[i]);
760 }
761 }
762 }
763
764 let s = &p;
766 let y = &g_new - &g;
767
768 let s_dot_y = s.dot(&y);
770 if s_dot_y > 1e-10 {
771 let s_col = s.clone().insert_axis(Axis(1));
772 let s_row = s.clone().insert_axis(Axis(0));
773
774 let bs = b.dot(s);
775 let bs_col = bs.clone().insert_axis(Axis(1));
776 let bs_row = bs.clone().insert_axis(Axis(0));
777
778 let term1 = s_dot_y + s.dot(&bs);
779 let term2 = &s_col.dot(&s_row) * (term1 / (s_dot_y * s_dot_y));
780
781 let term3 = &bs_col.dot(&s_row) + &s_col.dot(&bs_row);
782
783 b = &b + &term2 - &(&term3 / s_dot_y);
784 }
785
786 g = g_new;
788 a = a_new;
789 }
790
791 iter += 1;
792 }
793
794 let mut c_result = Array1::zeros(constraints.len());
796 for (i, constraint) in constraints.iter().enumerate() {
797 if !constraint.is_bounds() {
798 c_result[i] = c[i];
799 }
800 }
801
802 let mut result = OptimizeResults::default();
804 result.x = x;
805 result.fun = f;
806 result.jac = Some(g.into_raw_vec_and_offset().0);
807 result.constr = Some(c_result);
808 result.nfev = nfev;
809 result.nit = iter;
810 result.success = iter < maxiter;
811
812 if result.success {
813 result.message = "Optimization terminated successfully.".to_string();
814 } else {
815 result.message = "Maximum iterations reached.".to_string();
816 }
817
818 Ok(result)
819}
820
821fn compute_trust_region_step_constrained(
823 g: &Array1<f64>,
824 b: &Array2<f64>,
825 a: &Array2<f64>,
826 c: &Array1<f64>,
827 delta: f64,
828 constraints: &[Constraint<ConstraintFn>],
829 ctol: f64,
830) -> (Array1<f64>, f64) {
831 let n = g.len();
832 let n_constr = constraints.len();
833
834 let p_unconstrained = compute_unconstrained_cauchy_point(g, b, delta);
836
837 let mut constraint_violated = false;
839 for i in 0..n_constr {
840 if constraints[i].kind == ConstraintKind::Inequality {
841 let grad_c_dot_p = (0..n).map(|j| a[[i, j]] * p_unconstrained[j]).sum::<f64>();
842 if c[i] + grad_c_dot_p < -ctol {
843 constraint_violated = true;
844 break;
845 }
846 }
847 }
848
849 if !constraint_violated {
851 let g_dot_p = g.dot(&p_unconstrained);
853 let bp = b.dot(&p_unconstrained);
854 let p_dot_bp = p_unconstrained.dot(&bp);
855 let predicted_reduction = -g_dot_p - 0.5 * p_dot_bp;
856
857 return (p_unconstrained, predicted_reduction);
858 }
859
860 let mut p = Array1::zeros(n);
865 for i in 0..n {
866 p[i] = -g[i];
867 }
868
869 let p_norm = p.iter().map(|&pi| pi * pi).sum::<f64>().sqrt();
871 if p_norm > 1e-10 {
872 p = &p * (delta / p_norm);
873 }
874
875 for _iter in 0..5 {
877 let mut max_viol = 0.0;
879 let mut most_violated = 0;
880
881 for i in 0..n_constr {
883 if constraints[i].kind == ConstraintKind::Inequality {
884 let grad_c_dot_p = (0..n).map(|j| a[[i, j]] * p[j]).sum::<f64>();
885 let viol = -(c[i] + grad_c_dot_p);
886 if viol > max_viol {
887 max_viol = viol;
888 most_violated = i;
889 }
890 }
891 }
892
893 if max_viol < ctol {
894 break;
895 }
896
897 let mut a_norm_sq = 0.0;
899 for j in 0..n {
900 a_norm_sq += a[[most_violated, j]] * a[[most_violated, j]];
901 }
902
903 if a_norm_sq > 1e-10 {
904 let grad_c_dot_p = (0..n).map(|j| a[[most_violated, j]] * p[j]).sum::<f64>();
905 let proj_dist = (c[most_violated] + grad_c_dot_p) / a_norm_sq;
906
907 for j in 0..n {
909 p[j] += a[[most_violated, j]] * proj_dist;
910 }
911
912 let p_norm = p.iter().map(|&pi| pi * pi).sum::<f64>().sqrt();
914 if p_norm > delta {
915 p = &p * (delta / p_norm);
916 }
917 }
918 }
919
920 let g_dot_p = g.dot(&p);
922 let bp = b.dot(&p);
923 let p_dot_bp = p.dot(&bp);
924 let predicted_reduction = -g_dot_p - 0.5 * p_dot_bp;
925
926 (p, predicted_reduction)
927}
928
929fn compute_unconstrained_cauchy_point(g: &Array1<f64>, b: &Array2<f64>, delta: f64) -> Array1<f64> {
931 let n = g.len();
932
933 let mut p = Array1::zeros(n);
935 for i in 0..n {
936 p[i] = -g[i];
937 }
938
939 let bg = b.dot(g);
941 let g_dot_bg = g.dot(&bg);
942 let g_norm_sq = g.dot(g);
943
944 if g_norm_sq < 1e-10 {
946 return Array1::zeros(n);
948 }
949
950 let tau = if g_dot_bg <= 0.0 {
952 delta / g_norm_sq.sqrt()
954 } else {
955 f64::min(delta / g_norm_sq.sqrt(), g_norm_sq / g_dot_bg)
957 };
958
959 for i in 0..n {
961 p[i] *= tau;
962 }
963
964 p
965}
966
967#[cfg(test)]
968mod tests {
969 use super::*;
970 use ndarray::array;
971
972 fn objective(x: &[f64]) -> f64 {
973 (x[0] - 1.0).powi(2) + (x[1] - 2.5).powi(2)
974 }
975
976 fn constraint(x: &[f64]) -> f64 {
977 3.0 - x[0] - x[1] }
979
980 #[test]
981 fn test_minimize_constrained_placeholder() {
982 let x0 = array![0.0, 0.0];
984 let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
985
986 let options = Options {
988 maxiter: Some(1), ..Options::default()
990 };
991
992 let result = minimize_constrained(
993 objective,
994 &x0.view(),
995 &constraints,
996 Method::SLSQP,
997 Some(options),
998 )
999 .unwrap();
1000
1001 assert!(!result.success);
1003
1004 assert!(result.constr.is_some());
1006 let constr = result.constr.unwrap();
1007 assert_eq!(constr.len(), 1);
1008 }
1009
1010 #[test]
1012 fn test_minimize_slsqp() {
1013 let x0 = array![0.0, 0.0];
1018 let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
1019
1020 let options = Options {
1021 maxiter: Some(100),
1022 gtol: Some(1e-6),
1023 ftol: Some(1e-6),
1024 ctol: Some(1e-6),
1025 ..Options::default()
1026 };
1027
1028 let result = minimize_constrained(
1029 objective,
1030 &x0.view(),
1031 &constraints,
1032 Method::SLSQP,
1033 Some(options),
1034 )
1035 .unwrap();
1036
1037 assert!(result.x[0] >= 0.0);
1042 assert!(result.x[1] >= 0.0);
1043
1044 let initial_value = objective(&[0.0, 0.0]);
1046 assert!(result.fun <= initial_value);
1047
1048 assert!(result.constr.is_some());
1050
1051 println!(
1053 "SLSQP result: x = {:?}, f = {}, iterations = {}",
1054 result.x, result.fun, result.nit
1055 );
1056 }
1057
1058 #[test]
1060 fn test_minimize_trust_constr() {
1061 let x0 = array![0.0, 0.0];
1066 let constraints = vec![Constraint::new(constraint, Constraint::INEQUALITY)];
1067
1068 let options = Options {
1069 maxiter: Some(500), gtol: Some(1e-6),
1071 ftol: Some(1e-6),
1072 ctol: Some(1e-6),
1073 ..Options::default()
1074 };
1075
1076 let result = minimize_constrained(
1077 objective,
1078 &x0.view(),
1079 &constraints,
1080 Method::TrustConstr,
1081 Some(options.clone()),
1082 )
1083 .unwrap();
1084
1085 assert!(result.x[0] >= 0.0);
1087 assert!(result.x[1] >= 0.0);
1088
1089 let initial_value = objective(&[0.0, 0.0]);
1091 assert!(result.fun <= initial_value);
1092
1093 assert!(result.constr.is_some());
1095
1096 println!(
1098 "TrustConstr result: x = {:?}, f = {}, iterations = {}",
1099 result.x, result.fun, result.nit
1100 );
1101 }
1102
1103 #[test]
1105 fn test_constrained_rosenbrock() {
1106 fn rosenbrock(x: &[f64]) -> f64 {
1108 100.0 * (x[1] - x[0].powi(2)).powi(2) + (1.0 - x[0]).powi(2)
1109 }
1110
1111 fn circle_constraint(x: &[f64]) -> f64 {
1113 1.5 - (x[0].powi(2) + x[1].powi(2)) }
1115
1116 let x0 = array![0.0, 0.0];
1117 let constraints = vec![Constraint::new(circle_constraint, Constraint::INEQUALITY)];
1118
1119 let options = Options {
1120 maxiter: Some(1000), gtol: Some(1e-4), ftol: Some(1e-4),
1123 ctol: Some(1e-4),
1124 ..Options::default()
1125 };
1126
1127 let options_copy1 = options.clone();
1129 let options_copy2 = options.clone();
1130
1131 let result_slsqp = minimize_constrained(
1133 rosenbrock,
1134 &x0.view(),
1135 &constraints,
1136 Method::SLSQP,
1137 Some(options_copy1),
1138 )
1139 .unwrap();
1140
1141 let result_trust = minimize_constrained(
1143 rosenbrock,
1144 &x0.view(),
1145 &constraints,
1146 Method::TrustConstr,
1147 Some(options_copy2),
1148 )
1149 .unwrap();
1150
1151 println!(
1153 "SLSQP Rosenbrock result: x = {:?}, f = {}, iterations = {}",
1154 result_slsqp.x, result_slsqp.fun, result_slsqp.nit
1155 );
1156 println!(
1157 "TrustConstr Rosenbrock result: x = {:?}, f = {}, iterations = {}",
1158 result_trust.x, result_trust.fun, result_trust.nit
1159 );
1160
1161 let initial_value = rosenbrock(&[0.0, 0.0]);
1163 assert!(result_slsqp.fun < initial_value);
1164 assert!(result_trust.fun < initial_value);
1165
1166 let constr_slsqp = result_slsqp.constr.unwrap();
1168 let constr_trust = result_trust.constr.unwrap();
1169 assert!(constr_slsqp[0] >= -0.01); assert!(constr_trust[0] >= -0.01); }
1172}