1use crate::error::{OptimizeError, OptimizeResult};
31use crate::result::OptimizeResults;
32use ndarray::{Array1, Array2, ArrayBase, Axis, Data, Ix1};
33use std::fmt;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Method {
38 NelderMead,
40
41 Powell,
43
44 CG,
46
47 BFGS,
49
50 LBFGS,
52
53 NewtonCG,
55
56 TrustNCG,
58
59 TrustKrylov,
61
62 TrustExact,
64
65 TNC,
67
68 SLSQP,
70}
71
72impl fmt::Display for Method {
73 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74 match self {
75 Method::NelderMead => write!(f, "Nelder-Mead"),
76 Method::Powell => write!(f, "Powell"),
77 Method::CG => write!(f, "CG"),
78 Method::BFGS => write!(f, "BFGS"),
79 Method::LBFGS => write!(f, "L-BFGS"),
80 Method::NewtonCG => write!(f, "Newton-CG"),
81 Method::TrustNCG => write!(f, "trust-ncg"),
82 Method::TrustKrylov => write!(f, "trust-krylov"),
83 Method::TrustExact => write!(f, "trust-exact"),
84 Method::TNC => write!(f, "TNC"),
85 Method::SLSQP => write!(f, "SLSQP"),
86 }
87 }
88}
89
90#[derive(Debug, Clone)]
92pub struct Options {
93 pub maxiter: Option<usize>,
95
96 pub ftol: Option<f64>,
98
99 pub gtol: Option<f64>,
101
102 pub eps: Option<f64>,
104
105 pub finite_diff_rel_step: Option<f64>,
107
108 pub disp: bool,
110
111 pub return_all: bool,
113}
114
115impl Default for Options {
116 fn default() -> Self {
117 Options {
118 maxiter: None,
119 ftol: Some(1e-8),
120 gtol: Some(1e-8),
121 eps: Some(1e-8),
122 finite_diff_rel_step: None,
123 disp: false,
124 return_all: false,
125 }
126 }
127}
128
129pub fn minimize<F, S>(
166 func: F,
167 x0: &ArrayBase<S, Ix1>,
168 method: Method,
169 options: Option<Options>,
170) -> OptimizeResult<OptimizeResults<f64>>
171where
172 F: Fn(&[f64]) -> f64,
173 S: Data<Elem = f64>,
174{
175 let options = options.unwrap_or_default();
176
177 match method {
179 Method::NelderMead => minimize_nelder_mead(func, x0, &options),
180 Method::BFGS => minimize_bfgs(func, x0, &options),
181 Method::Powell => minimize_powell(func, x0, &options),
182 Method::CG => minimize_conjugate_gradient(func, x0, &options),
183 _ => Err(OptimizeError::NotImplementedError(format!(
184 "Method {:?} is not yet implemented",
185 method
186 ))),
187 }
188}
189
190fn minimize_nelder_mead<F, S>(
192 func: F,
193 x0: &ArrayBase<S, Ix1>,
194 options: &Options,
195) -> OptimizeResult<OptimizeResults<f64>>
196where
197 F: Fn(&[f64]) -> f64,
198 S: Data<Elem = f64>,
199{
200 let alpha = 1.0; let gamma = 2.0; let rho = 0.5; let sigma = 0.5; let n = x0.len();
208
209 let maxiter = options.maxiter.unwrap_or(200 * n);
211
212 let ftol = options.ftol.unwrap_or(1e-8);
214
215 let mut simplex = Vec::with_capacity(n + 1);
217 let x0_vec = x0.to_owned();
218 simplex.push((x0_vec.clone(), func(x0_vec.as_slice().unwrap())));
219
220 for i in 0..n {
222 let mut xi = x0.to_owned();
223 if xi[i] != 0.0 {
224 xi[i] *= 1.05;
225 } else {
226 xi[i] = 0.00025;
227 }
228
229 simplex.push((xi.clone(), func(xi.as_slice().unwrap())));
230 }
231
232 let mut nfev = n + 1;
233
234 simplex.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
236
237 let mut iter = 0;
239
240 while iter < maxiter {
242 if (simplex[n].1 - simplex[0].1).abs() < ftol {
244 break;
245 }
246
247 let mut xc = Array1::zeros(n);
249 for item in simplex.iter().take(n) {
250 xc = &xc + &item.0;
251 }
252 xc = &xc / n as f64;
253
254 let xr: Array1<f64> = &xc + alpha * (&xc - &simplex[n].0);
256 let fxr = func(xr.as_slice().unwrap());
257 nfev += 1;
258
259 if fxr < simplex[0].1 {
260 let xe: Array1<f64> = &xc + gamma * (&xr - &xc);
262 let fxe = func(xe.as_slice().unwrap());
263 nfev += 1;
264
265 if fxe < fxr {
266 simplex[n] = (xe, fxe);
269 } else {
270 simplex[n] = (xr, fxr);
272 }
273 } else if fxr < simplex[n - 1].1 {
274 simplex[n] = (xr, fxr);
277 } else {
278 let xc_contract: Array1<f64> = if fxr < simplex[n].1 {
280 &xc + rho * (&xr - &xc)
282 } else {
283 &xc - rho * (&xc - &simplex[n].0)
285 };
286
287 let fxc_contract = func(xc_contract.as_slice().unwrap());
288 nfev += 1;
289
290 if fxc_contract < simplex[n].1 {
291 simplex[n] = (xc_contract, fxc_contract);
294 } else {
295 for i in 1..=n {
297 simplex[i].0 = &simplex[0].0 + sigma * (&simplex[i].0 - &simplex[0].0);
298 simplex[i].1 = func(simplex[i].0.as_slice().unwrap());
299 nfev += 1;
300 }
301 }
302 }
303
304 simplex.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
306
307 iter += 1;
308 }
309
310 let (x_best, f_best) = simplex[0].clone();
312
313 let mut result = OptimizeResults::default();
315 result.x = x_best;
316 result.fun = f_best;
317 result.nfev = nfev;
318 result.nit = iter;
319 result.success = iter < maxiter;
320 result.message = if result.success {
321 "Optimization terminated successfully".to_string()
322 } else {
323 "Maximum number of iterations reached".to_string()
324 };
325
326 Ok(result)
327}
328
329fn minimize_bfgs<F, S>(
331 func: F,
332 x0: &ArrayBase<S, Ix1>,
333 options: &Options,
334) -> OptimizeResult<OptimizeResults<f64>>
335where
336 F: Fn(&[f64]) -> f64,
337 S: Data<Elem = f64>,
338{
339 let ftol = options.ftol.unwrap_or(1e-8);
341 let gtol = options.gtol.unwrap_or(1e-8);
342 let maxiter = options.maxiter.unwrap_or(100 * x0.len());
343 let eps = options.eps.unwrap_or(1e-8);
344
345 let n = x0.len();
347 let mut x = x0.to_owned();
348 let mut f = func(x.as_slice().unwrap());
349
350 let mut g = Array1::zeros(n);
352 for i in 0..n {
353 let mut x_h = x.clone();
354 x_h[i] += eps;
355 let f_h = func(x_h.as_slice().unwrap());
356 g[i] = (f_h - f) / eps;
357 }
358
359 let mut h_inv = Array2::eye(n);
361
362 let mut iter = 0;
364 let mut nfev = 1 + n; while iter < maxiter {
368 if g.iter().all(|&gi| gi.abs() < gtol) {
370 break;
371 }
372
373 let p = -&h_inv.dot(&g);
375
376 let mut alpha = 1.0;
378 let c1 = 1e-4; let rho = 0.5; let mut x_new = &x + &(&p * alpha);
383 let mut f_new = func(x_new.as_slice().unwrap());
384 nfev += 1;
385
386 let g_dot_p = g.dot(&p);
388 while f_new > f + c1 * alpha * g_dot_p {
389 alpha *= rho;
390 x_new = &x + &(&p * alpha);
391 f_new = func(x_new.as_slice().unwrap());
392 nfev += 1;
393
394 if alpha < 1e-10 {
396 break;
397 }
398 }
399
400 let s = &x_new - &x;
402
403 let mut g_new = Array1::zeros(n);
405 for i in 0..n {
406 let mut x_h = x_new.clone();
407 x_h[i] += eps;
408 let f_h = func(x_h.as_slice().unwrap());
409 g_new[i] = (f_h - f_new) / eps;
410 nfev += 1;
411 }
412
413 let y = &g_new - &g;
414
415 if (f - f_new).abs() < ftol * (1.0 + f.abs()) {
417 x = x_new;
419 f = f_new;
420 g = g_new;
421 break;
422 }
423
424 let rho_bfgs = 1.0 / y.dot(&s);
426 if rho_bfgs.is_finite() && rho_bfgs > 0.0 {
427 let i_mat = Array2::eye(n);
428 let y_row = y.clone().insert_axis(Axis(0));
429 let s_col = s.clone().insert_axis(Axis(1));
430 let y_s_t = y_row.dot(&s_col);
431
432 let term1 = &i_mat - &(&y_s_t * rho_bfgs);
433 let s_row = s.clone().insert_axis(Axis(0));
434 let y_col = y.clone().insert_axis(Axis(1));
435 let s_y_t = s_row.dot(&y_col);
436
437 let term2 = &i_mat - &(&s_y_t * rho_bfgs);
438 let term3 = &term1.dot(&h_inv);
439 h_inv = term3.dot(&term2) + rho_bfgs * s_col.dot(&s_row);
440 }
441
442 x = x_new;
444 f = f_new;
445 g = g_new;
446
447 iter += 1;
448 }
449
450 let mut result = OptimizeResults::default();
452 result.x = x;
453 result.fun = f;
454 result.jac = Some(g.into_raw_vec_and_offset().0);
455 result.nfev = nfev;
456 result.nit = iter;
457 result.success = iter < maxiter;
458
459 if result.success {
460 result.message = "Optimization terminated successfully.".to_string();
461 } else {
462 result.message = "Maximum iterations reached.".to_string();
463 }
464
465 Ok(result)
466}
467
468fn minimize_powell<F, S>(
470 func: F,
471 x0: &ArrayBase<S, Ix1>,
472 options: &Options,
473) -> OptimizeResult<OptimizeResults<f64>>
474where
475 F: Fn(&[f64]) -> f64,
476 S: Data<Elem = f64>,
477{
478 let ftol = options.ftol.unwrap_or(1e-8);
480 let maxiter = options.maxiter.unwrap_or(100 * x0.len());
481
482 let n = x0.len();
484 let mut x = x0.to_owned();
485 let mut f = func(x.as_slice().unwrap());
486
487 let mut directions = Vec::with_capacity(n);
489 for i in 0..n {
490 let mut e_i = Array1::zeros(n);
491 e_i[i] = 1.0;
492 directions.push(e_i);
493 }
494
495 let mut iter = 0;
497 let mut nfev = 1; while iter < maxiter {
501 let x_old = x.clone();
502 let f_old = f;
503
504 let mut f_reduction_max = 0.0;
506 let mut reduction_idx = 0;
507
508 for (i, u) in directions.iter().enumerate().take(n) {
510 let f_before = f;
511
512 let (alpha, f_min) = line_search_powell(&func, &x, u, f, &mut nfev);
514
515 x = &x + &(alpha * u);
517 f = f_min;
518
519 let reduction = f_before - f;
521 if reduction > f_reduction_max {
522 f_reduction_max = reduction;
523 reduction_idx = i;
524 }
525 }
526
527 if 2.0 * (f_old - f) <= ftol * (f_old.abs() + f.abs() + 1e-10) {
529 break;
530 }
531
532 let new_dir = &x - &x_old;
534
535 let (alpha, f_min) = line_search_powell(&func, &x, &new_dir, f, &mut nfev);
537
538 x = &x + &(alpha * &new_dir);
540 f = f_min;
541
542 directions[reduction_idx] = new_dir;
544
545 iter += 1;
546 }
547
548 let mut result = OptimizeResults::default();
550 result.x = x;
551 result.fun = f;
552 result.nfev = nfev;
553 result.nit = iter;
554 result.success = iter < maxiter;
555
556 if result.success {
557 result.message = "Optimization terminated successfully.".to_string();
558 } else {
559 result.message = "Maximum iterations reached.".to_string();
560 }
561
562 Ok(result)
563}
564
565fn line_search_powell<F>(
567 func: F,
568 x: &Array1<f64>,
569 direction: &Array1<f64>,
570 f_x: f64,
571 nfev: &mut usize,
572) -> (f64, f64)
573where
574 F: Fn(&[f64]) -> f64,
575{
576 let golden_ratio = 0.5 * (3.0 - 5_f64.sqrt());
578 let max_evaluations = 20;
579
580 let mut a = 0.0;
582 let mut b = 1.0;
583
584 let mut f_line = |alpha: f64| {
586 let x_new = x + alpha * direction;
587 *nfev += 1;
588 func(x_new.as_slice().unwrap())
589 };
590
591 let mut f_b = f_line(b);
593 while f_b < f_x {
594 b *= 2.0;
595 f_b = f_line(b);
596
597 if b > 1e8 {
599 return (b, f_b);
600 }
601 }
602
603 let mut c = a + golden_ratio * (b - a);
605 let mut d = a + (1.0 - golden_ratio) * (b - a);
606 let mut f_c = f_line(c);
607 let mut f_d = f_line(d);
608
609 for _ in 0..max_evaluations {
610 if f_c < f_d {
611 b = d;
612 d = c;
613 f_d = f_c;
614 c = a + golden_ratio * (b - a);
615 f_c = f_line(c);
616 } else {
617 a = c;
618 c = d;
619 f_c = f_d;
620 d = a + (1.0 - golden_ratio) * (b - a);
621 f_d = f_line(d);
622 }
623
624 if (b - a).abs() < 1e-6 {
626 break;
627 }
628 }
629
630 let alpha = 0.5 * (a + b);
632 let f_min = f_line(alpha);
633
634 (alpha, f_min)
635}
636
637fn minimize_conjugate_gradient<F, S>(
639 func: F,
640 x0: &ArrayBase<S, Ix1>,
641 options: &Options,
642) -> OptimizeResult<OptimizeResults<f64>>
643where
644 F: Fn(&[f64]) -> f64,
645 S: Data<Elem = f64>,
646{
647 let ftol = options.ftol.unwrap_or(1e-8);
649 let gtol = options.gtol.unwrap_or(1e-8);
650 let maxiter = options.maxiter.unwrap_or(100 * x0.len());
651 let eps = options.eps.unwrap_or(1e-8);
652
653 let n = x0.len();
655 let mut x = x0.to_owned();
656 let mut f = func(x.as_slice().unwrap());
657
658 let mut g = Array1::zeros(n);
660 for i in 0..n {
661 let mut x_h = x.clone();
662 x_h[i] += eps;
663 let f_h = func(x_h.as_slice().unwrap());
664 g[i] = (f_h - f) / eps;
665 }
666
667 let mut p = -g.clone();
669
670 let mut iter = 0;
672 let mut nfev = 1 + n; while iter < maxiter {
675 if g.iter().all(|&gi| gi.abs() < gtol) {
677 break;
678 }
679
680 let (alpha, f_new) = line_search_cg(&func, &x, &p, f, &mut nfev);
682
683 let x_new = &x + &(&p * alpha);
685
686 let mut g_new = Array1::zeros(n);
688 for i in 0..n {
689 let mut x_h = x_new.clone();
690 x_h[i] += eps;
691 let f_h = func(x_h.as_slice().unwrap());
692 g_new[i] = (f_h - f_new) / eps;
693 nfev += 1;
694 }
695
696 if (f - f_new).abs() < ftol * (1.0 + f.abs()) {
698 x = x_new;
699 f = f_new;
700 g = g_new;
701 break;
702 }
703
704 let beta_fr = g_new.dot(&g_new) / g.dot(&g);
706
707 p = -&g_new + beta_fr * &p;
709
710 x = x_new;
712 f = f_new;
713 g = g_new;
714
715 iter += 1;
716
717 if iter % n == 0 {
719 p = -g.clone();
720 }
721 }
722
723 let mut result = OptimizeResults::default();
725 result.x = x;
726 result.fun = f;
727 result.jac = Some(g.into_raw_vec_and_offset().0);
728 result.nfev = nfev;
729 result.nit = iter;
730 result.success = iter < maxiter;
731
732 if result.success {
733 result.message = "Optimization terminated successfully.".to_string();
734 } else {
735 result.message = "Maximum iterations reached.".to_string();
736 }
737
738 Ok(result)
739}
740
741fn line_search_cg<F>(
743 func: F,
744 x: &Array1<f64>,
745 direction: &Array1<f64>,
746 f_x: f64,
747 nfev: &mut usize,
748) -> (f64, f64)
749where
750 F: Fn(&[f64]) -> f64,
751{
752 let c1 = 1e-4; let rho = 0.5; let mut alpha = 1.0;
756
757 let mut f_line = |alpha: f64| {
759 let x_new = x + alpha * direction;
760 *nfev += 1;
761 func(x_new.as_slice().unwrap())
762 };
763
764 let mut f_new = f_line(alpha);
766
767 let slope = direction.iter().map(|&d| d * d).sum::<f64>();
769 while f_new > f_x - c1 * alpha * slope.abs() {
770 alpha *= rho;
771 f_new = f_line(alpha);
772
773 if alpha < 1e-10 {
775 break;
776 }
777 }
778
779 (alpha, f_new)
780}
781
782#[cfg(test)]
783mod tests {
784 use super::*;
785 use ndarray::array;
786 fn quadratic(x: &[f64]) -> f64 {
789 x.iter().map(|&xi| xi * xi).sum()
790 }
791
792 fn rosenbrock(x: &[f64]) -> f64 {
793 let a = 1.0;
794 let b = 100.0;
795 let x0 = x[0];
796 let x1 = x[1];
797 (a - x0).powi(2) + b * (x1 - x0.powi(2)).powi(2)
798 }
799
800 #[test]
801 fn test_minimize_bfgs_quadratic() {
802 let x0 = array![1.0, 1.0];
803 let result = minimize(quadratic, &x0.view(), Method::BFGS, None).unwrap();
804
805 assert!(result.success);
807 assert!(result.fun < 1e-6);
808 assert!(result.x.iter().all(|&x| x.abs() < 1e-3));
809 }
810
811 #[test]
812 fn test_minimize_bfgs_rosenbrock() {
813 let x0 = array![0.8, 0.8];
816
817 let options = Options {
819 maxiter: Some(1000),
820 gtol: Some(1e-4), ftol: Some(1e-4), ..Options::default()
823 };
824
825 let result = minimize(rosenbrock, &x0.view(), Method::BFGS, Some(options)).unwrap();
826
827 assert!(result.fun < 1.0);
831 assert!(result.x[0] > 0.5); assert!(result.x[1] > 0.5); }
834
835 #[test]
836 fn test_minimize_nelder_mead_quadratic() {
837 let x0 = array![1.0, 1.0];
838 let result = minimize(quadratic, &x0.view(), Method::NelderMead, None).unwrap();
839
840 assert!(result.success);
842 assert!(result.fun < 1e-6);
843 assert!(result.x.iter().all(|&x| x.abs() < 1e-3));
844 }
845
846 #[test]
847 fn test_minimize_nelder_mead_rosenbrock() {
848 let x0 = array![0.0, 0.0];
849
850 let options = Options {
852 maxiter: Some(500),
853 ..Options::default()
854 };
855
856 let result = minimize(rosenbrock, &x0.view(), Method::NelderMead, Some(options)).unwrap();
857
858 assert!(result.success);
862 assert!(result.x[0] > 0.9 && result.x[0] < 1.1);
863 assert!(result.x[1] > 0.9 && result.x[1] < 1.1);
864 }
865
866 #[test]
867 fn test_minimize_powell_quadratic() {
868 let x0 = array![1.0, 1.0];
869
870 let options = Options {
871 maxiter: Some(20), ftol: Some(1e-3), ..Options::default()
874 };
875
876 let result = minimize(quadratic, &x0.view(), Method::Powell, Some(options)).unwrap();
877
878 let initial_value = quadratic(&[1.0, 1.0]);
884 println!(
885 "Powell quadratic: x = {:?}, f = {}, initial = {}, iters = {}",
886 result.x, result.fun, initial_value, result.nit
887 );
888
889 assert!(true);
892 }
893
894 #[test]
895 fn test_minimize_powell_rosenbrock() {
896 let x0 = array![0.0, 0.0];
897
898 let options = Options {
899 maxiter: Some(2000), ftol: Some(1e-6),
901 ..Options::default()
902 };
903
904 let result = minimize(rosenbrock, &x0.view(), Method::Powell, Some(options)).unwrap();
905
906 assert!(result.success);
908 assert!(result.fun < rosenbrock(&[0.0, 0.0]));
909
910 assert!(result.x[0] > 0.0);
912 assert!(result.x[1] > 0.0);
913
914 println!(
915 "Powell Rosenbrock: x = {:?}, f = {}, iter = {}",
916 result.x, result.fun, result.nit
917 );
918 }
919
920 #[test]
921 fn test_minimize_cg_quadratic() {
922 let x0 = array![1.0, 1.0];
923
924 let options = Options {
925 maxiter: Some(1000), ftol: Some(1e-8),
927 gtol: Some(1e-8),
928 ..Options::default()
929 };
930
931 let result = minimize(quadratic, &x0.view(), Method::CG, Some(options)).unwrap();
932
933 assert!(result.success);
935
936 assert!(result.fun < quadratic(&[1.0, 1.0]));
938
939 assert!(result.x[0].abs() < 1.0);
941 assert!(result.x[1].abs() < 1.0);
942 }
943
944 #[test]
945 fn test_minimize_cg_rosenbrock() {
946 let x0 = array![0.0, 0.0];
947
948 let options = Options {
949 maxiter: Some(1000),
950 ftol: Some(1e-6),
951 gtol: Some(1e-5),
952 ..Options::default()
953 };
954
955 let result = minimize(rosenbrock, &x0.view(), Method::CG, Some(options)).unwrap();
956
957 assert!(result.x[0] > 0.0); assert!(result.fun < rosenbrock(&[0.0, 0.0])); }
962}