1use crate::error::{OptimizeError, OptimizeResult};
41use crate::result::OptimizeResults;
42use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Data, Ix1};
43use std::fmt;
44
45use crate::roots_anderson::root_anderson;
47use crate::roots_krylov::root_krylov;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum Method {
52 Hybr,
55
56 Lm,
58
59 Broyden1,
61
62 Broyden2,
64
65 Anderson,
67
68 KrylovLevenbergMarquardt,
70
71 Scalar,
73}
74
75impl fmt::Display for Method {
76 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
77 match self {
78 Method::Hybr => write!(f, "hybr"),
79 Method::Lm => write!(f, "lm"),
80 Method::Broyden1 => write!(f, "broyden1"),
81 Method::Broyden2 => write!(f, "broyden2"),
82 Method::Anderson => write!(f, "anderson"),
83 Method::KrylovLevenbergMarquardt => write!(f, "krylov"),
84 Method::Scalar => write!(f, "scalar"),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct Options {
92 pub maxfev: Option<usize>,
94
95 pub xtol: Option<f64>,
97
98 pub ftol: Option<f64>,
100
101 pub gtol: Option<f64>,
103
104 pub disp: bool,
106
107 pub eps: Option<f64>,
109
110 pub m_anderson: Option<usize>,
112
113 pub beta_anderson: Option<f64>,
115}
116
117impl Default for Options {
118 fn default() -> Self {
119 Options {
120 maxfev: None,
121 xtol: Some(1e-8),
122 ftol: Some(1e-8),
123 gtol: Some(1e-8),
124 disp: false,
125 eps: Some(1e-8),
126 m_anderson: Some(5), beta_anderson: Some(0.5), }
129 }
130}
131
132#[allow(dead_code)]
183pub fn root<F, J, S>(
184 func: F,
185 x0: &ArrayBase<S, Ix1>,
186 method: Method,
187 jac: Option<J>,
188 options: Option<Options>,
189) -> OptimizeResult<OptimizeResults<f64>>
190where
191 F: Fn(&[f64]) -> Array1<f64>,
192 J: Fn(&[f64]) -> Array2<f64>,
193 S: Data<Elem = f64>,
194{
195 let options = options.unwrap_or_default();
196
197 match method {
199 Method::Hybr => root_hybr(func, x0, jac, &options),
200 Method::Lm => root_levenberg_marquardt(func, x0, jac, &options),
201 Method::Broyden1 => root_broyden1(func, x0, jac, &options),
202 Method::Broyden2 => {
203 root_broyden2(func, x0, jac, &options)
205 }
206 Method::Anderson => {
207 root_anderson(func, x0, jac, &options)
209 }
210 Method::KrylovLevenbergMarquardt => {
211 root_krylov(func, x0, jac, &options)
213 }
214 Method::Scalar => root_scalar(func, x0, jac, &options),
215 }
216}
217
218#[allow(dead_code)]
220fn root_hybr<F, J, S>(
221 func: F,
222 x0: &ArrayBase<S, Ix1>,
223 jacobian_fn: Option<J>,
224 options: &Options,
225) -> OptimizeResult<OptimizeResults<f64>>
226where
227 F: Fn(&[f64]) -> Array1<f64>,
228 J: Fn(&[f64]) -> Array2<f64>,
229 S: Data<Elem = f64>,
230{
231 let xtol = options.xtol.unwrap_or(1e-8);
233 let ftol = options.ftol.unwrap_or(1e-8);
234 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
235 let eps = options.eps.unwrap_or(1e-8);
236
237 let n = x0.len();
239 let mut x = x0.to_owned();
240 let mut f = func(x.as_slice().expect("Operation failed"));
241 let mut nfev = 1;
242 let mut njev = 0;
243
244 let compute_numerical_jac =
246 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
247 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
248 let mut count = 0;
249
250 for j in 0..x_values.len() {
251 let mut x_h = Vec::from(x_values);
252 x_h[j] += eps;
253 let f_h = func(&x_h);
254 count += 1;
255
256 for i in 0..f_values.len() {
257 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
258 }
259 }
260
261 (jac, count)
262 };
263
264 let get_jacobian = |x_values: &[f64],
266 f_values: &Array1<f64>,
267 jac_fn: &Option<J>|
268 -> (Array2<f64>, usize, usize) {
269 match jac_fn {
270 Some(func) => {
271 let j = func(x_values);
272 (j, 0, 1)
273 }
274 None => {
275 let (j, count) = compute_numerical_jac(x_values, f_values);
276 (j, count, 0)
277 }
278 }
279 };
280
281 let (mut jac, nfev_inc, njev_inc) =
283 get_jacobian(x.as_slice().expect("Operation failed"), &f, &jacobian_fn);
284 nfev += nfev_inc;
285 njev += njev_inc;
286
287 let mut iter = 0;
289 let mut converged = false;
290
291 while iter < maxfev {
292 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
294 if f_norm < ftol {
295 converged = true;
296 break;
297 }
298
299 let delta = match solve(&jac, &(-&f)) {
301 Some(d) => d,
302 None => {
303 let mut gradient = Array1::zeros(n);
306 for i in 0..n {
307 for j in 0..f.len() {
308 gradient[i] += jac[[j, i]] * f[j];
309 }
310 }
311
312 let step_size = 0.1
313 / (1.0
314 + gradient
315 .iter()
316 .map(|&g: &f64| g.powi(2))
317 .sum::<f64>()
318 .sqrt());
319 -gradient * step_size
320 }
321 };
322
323 let mut x_new = x.clone();
325 for i in 0..n {
326 x_new[i] += delta[i];
327 }
328
329 let mut alpha = 1.0;
331 let mut f_new = func(x_new.as_slice().expect("Operation failed"));
332 nfev += 1;
333
334 let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
335 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
336
337 let max_backtrack = 5;
339 let mut backtrack_count = 0;
340
341 while f_new_norm > f_norm && backtrack_count < max_backtrack {
342 alpha *= 0.5;
343 backtrack_count += 1;
344
345 for i in 0..n {
347 x_new[i] = x[i] + alpha * delta[i];
348 }
349
350 f_new = func(x_new.as_slice().expect("Operation failed"));
352 nfev += 1;
353 f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
354 }
355
356 let step_norm = (0..n)
358 .map(|i| (x_new[i] - x[i]).powi(2))
359 .sum::<f64>()
360 .sqrt();
361 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
362
363 if step_norm < xtol * (1.0 + x_norm) {
364 converged = true;
365 x = x_new;
366 f = f_new;
367 break;
368 }
369
370 x = x_new;
372 f = f_new;
373
374 let (new_jac, nfev_delta, njev_delta) =
376 get_jacobian(x.as_slice().expect("Operation failed"), &f, &jacobian_fn);
377 jac = new_jac;
378 nfev += nfev_delta;
379 njev += njev_delta;
380
381 iter += 1;
382 }
383
384 let mut result = OptimizeResults::default();
386 result.x = x;
387 result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
388
389 let (jac_vec, _) = jac.into_raw_vec_and_offset();
391 result.jac = Some(jac_vec);
392
393 result.nfev = nfev;
394 result.njev = njev;
395 result.nit = iter;
396 result.success = converged;
397
398 if converged {
399 result.message = "Root finding converged successfully".to_string();
400 } else {
401 result.message = "Maximum number of function evaluations reached".to_string();
402 }
403
404 Ok(result)
405}
406
407#[allow(dead_code)]
409fn solve(a: &Array2<f64>, b: &Array1<f64>) -> Option<Array1<f64>> {
410 use scirs2_linalg::solve;
411
412 solve(&a.view(), &b.view(), None).ok()
413}
414
415#[allow(dead_code)]
417fn root_broyden1<F, J, S>(
418 func: F,
419 x0: &ArrayBase<S, Ix1>,
420 jacobian_fn: Option<J>,
421 options: &Options,
422) -> OptimizeResult<OptimizeResults<f64>>
423where
424 F: Fn(&[f64]) -> Array1<f64>,
425 J: Fn(&[f64]) -> Array2<f64>,
426 S: Data<Elem = f64>,
427{
428 let xtol = options.xtol.unwrap_or(1e-8);
430 let ftol = options.ftol.unwrap_or(1e-8);
431 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
432 let eps = options.eps.unwrap_or(1e-8);
433
434 let n = x0.len();
436 let mut x = x0.to_owned();
437 let mut f = func(x.as_slice().expect("Operation failed"));
438 let mut nfev = 1;
439 let mut njev = 0;
440
441 let compute_numerical_jac =
443 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
444 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
445 let mut count = 0;
446
447 for j in 0..x_values.len() {
448 let mut x_h = Vec::from(x_values);
449 x_h[j] += eps;
450 let f_h = func(&x_h);
451 count += 1;
452
453 for i in 0..f_values.len() {
454 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
455 }
456 }
457
458 (jac, count)
459 };
460
461 let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
463 Some(jac_fn) => {
464 let j = jac_fn(x.as_slice().expect("Operation failed"));
465 (j, 0, 1)
466 }
467 None => {
468 let (j, count) = compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
469 (j, count, 0)
470 }
471 };
472
473 nfev += nfev_inc;
474 njev += njev_inc;
475
476 let mut iter = 0;
478 let mut converged = false;
479
480 while iter < maxfev {
483 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
485 if f_norm < ftol {
486 converged = true;
487 break;
488 }
489
490 let delta = match solve(&jac, &(-&f)) {
492 Some(d) => d,
493 None => {
494 let mut gradient = Array1::zeros(n);
497 for i in 0..n {
498 for j in 0..f.len() {
499 gradient[i] += jac[[j, i]] * f[j];
500 }
501 }
502
503 let step_size = 0.1
504 / (1.0
505 + gradient
506 .iter()
507 .map(|&g: &f64| g.powi(2))
508 .sum::<f64>()
509 .sqrt());
510 -gradient * step_size
511 }
512 };
513
514 let mut x_new = x.clone();
516 for i in 0..n {
517 x_new[i] += delta[i];
518 }
519
520 let mut alpha = 1.0;
522 let mut f_new = func(x_new.as_slice().expect("Operation failed"));
523 nfev += 1;
524
525 let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
526 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
527
528 let max_backtrack = 5;
530 let mut backtrack_count = 0;
531
532 while f_new_norm > f_norm && backtrack_count < max_backtrack {
533 alpha *= 0.5;
534 backtrack_count += 1;
535
536 for i in 0..n {
538 x_new[i] = x[i] + alpha * delta[i];
539 }
540
541 f_new = func(x_new.as_slice().expect("Operation failed"));
543 nfev += 1;
544 f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
545 }
546
547 let step_norm = (0..n)
549 .map(|i| (x_new[i] - x[i]).powi(2))
550 .sum::<f64>()
551 .sqrt();
552 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
553
554 if step_norm < xtol * (1.0 + x_norm) {
555 converged = true;
556 x = x_new;
557 f = f_new;
558 break;
559 }
560
561 let mut s: Array1<f64> = Array1::zeros(n);
566 for i in 0..n {
567 s[i] = x_new[i] - x[i];
568 }
569
570 let mut y: Array1<f64> = Array1::zeros(f.len());
572 for i in 0..f.len() {
573 y[i] = f_new[i] - f[i];
574 }
575
576 let mut js: Array1<f64> = Array1::zeros(f.len());
578 for i in 0..f.len() {
579 for j in 0..n {
580 js[i] += jac[[i, j]] * s[j];
581 }
582 }
583
584 let mut residual: Array1<f64> = Array1::zeros(f.len());
586 for i in 0..f.len() {
587 residual[i] = y[i] - js[i];
588 }
589
590 let s_norm_squared = s.iter().map(|&si| si.powi(2)).sum::<f64>();
592
593 if s_norm_squared > 1e-14 {
594 for i in 0..f.len() {
597 for j in 0..n {
598 jac[[i, j]] += residual[i] * s[j] / s_norm_squared;
599 }
600 }
601 }
602
603 x = x_new;
605 f = f_new;
606
607 iter += 1;
608 }
609
610 let mut result = OptimizeResults::default();
612 result.x = x;
613 result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
614
615 let (jac_vec, _) = jac.into_raw_vec_and_offset();
617 result.jac = Some(jac_vec);
618
619 result.nfev = nfev;
620 result.njev = njev;
621 result.nit = iter;
622 result.success = converged;
623
624 if converged {
625 result.message = "Root finding converged successfully".to_string();
626 } else {
627 result.message = "Maximum number of function evaluations reached".to_string();
628 }
629
630 Ok(result)
631}
632
633#[allow(dead_code)]
635fn root_broyden2<F, J, S>(
636 func: F,
637 x0: &ArrayBase<S, Ix1>,
638 jacobian_fn: Option<J>,
639 options: &Options,
640) -> OptimizeResult<OptimizeResults<f64>>
641where
642 F: Fn(&[f64]) -> Array1<f64>,
643 J: Fn(&[f64]) -> Array2<f64>,
644 S: Data<Elem = f64>,
645{
646 let xtol = options.xtol.unwrap_or(1e-8);
648 let ftol = options.ftol.unwrap_or(1e-8);
649 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
650 let eps = options.eps.unwrap_or(1e-8);
651
652 let n = x0.len();
654 let mut x = x0.to_owned();
655 let mut f = func(x.as_slice().expect("Operation failed"));
656 let mut nfev = 1;
657 let mut njev = 0;
658
659 let compute_numerical_jac =
661 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
662 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
663 let mut count = 0;
664
665 for j in 0..x_values.len() {
666 let mut x_h = Vec::from(x_values);
667 x_h[j] += eps;
668 let f_h = func(&x_h);
669 count += 1;
670
671 for i in 0..f_values.len() {
672 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
673 }
674 }
675
676 (jac, count)
677 };
678
679 let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
681 Some(jac_fn) => {
682 let j = jac_fn(x.as_slice().expect("Operation failed"));
683 (j, 0, 1)
684 }
685 None => {
686 let (j, count) = compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
687 (j, count, 0)
688 }
689 };
690
691 nfev += nfev_inc;
692 njev += njev_inc;
693
694 let mut iter = 0;
696 let mut converged = false;
697
698 while iter < maxfev {
699 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
701 if f_norm < ftol {
702 converged = true;
703 break;
704 }
705
706 let delta = match solve(&jac, &(-&f)) {
708 Some(d) => d,
709 None => {
710 let mut gradient = Array1::zeros(n);
713 for i in 0..n {
714 for j in 0..f.len() {
715 gradient[i] += jac[[j, i]] * f[j];
716 }
717 }
718
719 let step_size = 0.1
720 / (1.0
721 + gradient
722 .iter()
723 .map(|&g: &f64| g.powi(2))
724 .sum::<f64>()
725 .sqrt());
726 -gradient * step_size
727 }
728 };
729
730 let mut x_new = x.clone();
732 for i in 0..n {
733 x_new[i] += delta[i];
734 }
735
736 let mut alpha = 1.0;
738 let mut f_new = func(x_new.as_slice().expect("Operation failed"));
739 nfev += 1;
740
741 let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
742 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
743
744 let max_backtrack = 5;
746 let mut backtrack_count = 0;
747
748 while f_new_norm > f_norm && backtrack_count < max_backtrack {
749 alpha *= 0.5;
750 backtrack_count += 1;
751
752 for i in 0..n {
754 x_new[i] = x[i] + alpha * delta[i];
755 }
756
757 f_new = func(x_new.as_slice().expect("Operation failed"));
759 nfev += 1;
760 f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
761 }
762
763 let step_norm = (0..n)
765 .map(|i| (x_new[i] - x[i]).powi(2))
766 .sum::<f64>()
767 .sqrt();
768 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
769
770 if step_norm < xtol * (1.0 + x_norm) {
771 converged = true;
772 x = x_new;
773 f = f_new;
774 break;
775 }
776
777 let mut s: Array1<f64> = Array1::zeros(n);
779 for i in 0..n {
780 s[i] = x_new[i] - x[i];
781 }
782
783 let mut y: Array1<f64> = Array1::zeros(f.len());
785 for i in 0..f.len() {
786 y[i] = f_new[i] - f[i];
787 }
788
789 let mut js: Array1<f64> = Array1::zeros(f.len());
794 for i in 0..f.len() {
795 for j in 0..n {
796 js[i] += jac[[i, j]] * s[j];
797 }
798 }
799
800 let mut residual: Array1<f64> = Array1::zeros(f.len());
802 for i in 0..f.len() {
803 residual[i] = y[i] - js[i];
804 }
805
806 let mut jtjs: Array1<f64> = Array1::zeros(n);
808
809 let mut jtj: Array2<f64> = Array2::zeros((n, n));
811 for i in 0..n {
812 for j in 0..n {
813 for k in 0..f.len() {
814 jtj[[i, j]] += jac[[k, i]] * jac[[k, j]];
815 }
816 }
817 }
818
819 for i in 0..n {
821 for j in 0..n {
822 jtjs[i] += jtj[[i, j]] * s[j];
823 }
824 }
825
826 let mut denominator: f64 = 0.0;
828 for i in 0..n {
829 denominator += s[i] * jtjs[i];
830 }
831
832 if denominator.abs() > 1e-14 {
834 for i in 0..f.len() {
836 for j in 0..n {
837 let mut change: f64 = 0.0;
839 for k in 0..n {
840 change += residual[i] * s[k] * jac[[i, k]];
841 }
842 jac[[i, j]] += change * s[j] / denominator;
843 }
844 }
845 }
846
847 x = x_new;
849 f = f_new;
850
851 iter += 1;
852 }
853
854 let mut result = OptimizeResults::default();
856 result.x = x;
857 result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
858
859 let (jac_vec, _) = jac.into_raw_vec_and_offset();
861 result.jac = Some(jac_vec);
862
863 result.nfev = nfev;
864 result.njev = njev;
865 result.nit = iter;
866 result.success = converged;
867
868 if converged {
869 result.message = "Root finding converged successfully".to_string();
870 } else {
871 result.message = "Maximum number of function evaluations reached".to_string();
872 }
873
874 Ok(result)
875}
876
877#[allow(dead_code)]
883fn root_levenberg_marquardt<F, J, S>(
884 func: F,
885 x0: &ArrayBase<S, Ix1>,
886 jacobian_fn: Option<J>,
887 options: &Options,
888) -> OptimizeResult<OptimizeResults<f64>>
889where
890 F: Fn(&[f64]) -> Array1<f64>,
891 J: Fn(&[f64]) -> Array2<f64>,
892 S: Data<Elem = f64>,
893{
894 let xtol = options.xtol.unwrap_or(1e-8);
896 let ftol = options.ftol.unwrap_or(1e-8);
897 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
898 let eps = options.eps.unwrap_or(1e-8);
899
900 let n = x0.len();
902 let mut x = x0.to_owned();
903 let mut f = func(x.as_slice().expect("Operation failed"));
904 let mut nfev = 1;
905 let mut njev = 0;
906
907 let mut lambda = 1e-3; let lambda_factor = 10.0; let compute_numerical_jac =
913 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
914 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
915 let mut count = 0;
916
917 for j in 0..x_values.len() {
918 let mut x_h = Vec::from(x_values);
919 x_h[j] += eps;
920 let f_h = func(&x_h);
921 count += 1;
922
923 for i in 0..f_values.len() {
924 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
925 }
926 }
927
928 (jac, count)
929 };
930
931 let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
933 Some(jac_fn) => {
934 let j = jac_fn(x.as_slice().expect("Operation failed"));
935 (j, 0, 1)
936 }
937 None => {
938 let (j, count) = compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
939 (j, count, 0)
940 }
941 };
942
943 nfev += nfev_inc;
944 njev += njev_inc;
945
946 let mut iter = 0;
948 let mut converged = false;
949 let mut current_cost = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
950
951 while iter < maxfev {
952 let f_norm = current_cost.sqrt();
954 if f_norm < ftol {
955 converged = true;
956 break;
957 }
958
959 let mut jtj = Array2::zeros((n, n));
961 let mut jtf = Array1::zeros(n);
962
963 for i in 0..n {
964 for j in 0..n {
965 for k in 0..f.len() {
966 jtj[[i, j]] += jac[[k, i]] * jac[[k, j]];
967 }
968 }
969 for k in 0..f.len() {
970 jtf[i] += jac[[k, i]] * f[k];
971 }
972 }
973
974 for i in 0..n {
976 jtj[[i, i]] += lambda;
977 }
978
979 let neg_jtf = jtf.mapv(|x: f64| -x);
981 let delta = match solve(&jtj, &neg_jtf) {
982 Some(d) => d,
983 None => {
984 lambda *= lambda_factor;
986 for i in 0..n {
987 jtj[[i, i]] += lambda;
988 }
989 let neg_jtf2 = jtf.mapv(|x| -x);
990 match solve(&jtj, &neg_jtf2) {
991 Some(d) => d,
992 None => {
993 let step_size =
995 0.1 / (1.0 + jtf.iter().map(|&g| g.powi(2)).sum::<f64>().sqrt());
996 jtf.mapv(|x| -x * step_size)
997 }
998 }
999 }
1000 };
1001
1002 let mut x_new = x.clone();
1004 for i in 0..n {
1005 x_new[i] += delta[i];
1006 }
1007
1008 let f_new = func(x_new.as_slice().expect("Operation failed"));
1010 nfev += 1;
1011 let new_cost = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>();
1012
1013 if new_cost < current_cost {
1015 let improvement = (current_cost - new_cost) / current_cost;
1017
1018 let step_norm = (0..n)
1020 .map(|i| (x_new[i] - x[i]).powi(2))
1021 .sum::<f64>()
1022 .sqrt();
1023 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
1024
1025 if step_norm < xtol * (1.0 + x_norm) || improvement < ftol {
1026 converged = true;
1027 x = x_new;
1028 f = f_new;
1029 current_cost = new_cost;
1030 break;
1031 }
1032
1033 x = x_new;
1035 f = f_new;
1036 current_cost = new_cost;
1037
1038 lambda = f64::max(lambda / lambda_factor, 1e-12);
1040
1041 let (new_jac, nfev_delta, njev_delta) = match &jacobian_fn {
1043 Some(jac_fn) => {
1044 let j = jac_fn(x.as_slice().expect("Operation failed"));
1045 (j, 0, 1)
1046 }
1047 None => {
1048 let (j, count) =
1049 compute_numerical_jac(x.as_slice().expect("Operation failed"), &f);
1050 (j, count, 0)
1051 }
1052 };
1053 jac = new_jac;
1054 nfev += nfev_delta;
1055 njev += njev_delta;
1056 } else {
1057 lambda = f64::min(lambda * lambda_factor, 1e12);
1059 }
1060
1061 iter += 1;
1062 }
1063
1064 let mut result = OptimizeResults::default();
1066 result.x = x;
1067 result.fun = current_cost;
1068
1069 let (jac_vec, _) = jac.into_raw_vec_and_offset();
1071 result.jac = Some(jac_vec);
1072
1073 result.nfev = nfev;
1074 result.njev = njev;
1075 result.nit = iter;
1076 result.success = converged;
1077
1078 if converged {
1079 result.message = "Levenberg-Marquardt converged successfully".to_string();
1080 } else {
1081 result.message = "Maximum number of function evaluations reached".to_string();
1082 }
1083
1084 Ok(result)
1085}
1086
1087#[allow(dead_code)]
1092fn root_scalar<F, J, S>(
1093 func: F,
1094 x0: &ArrayBase<S, Ix1>,
1095 jacobian_fn: Option<J>,
1096 options: &Options,
1097) -> OptimizeResult<OptimizeResults<f64>>
1098where
1099 F: Fn(&[f64]) -> Array1<f64>,
1100 J: Fn(&[f64]) -> Array2<f64>,
1101 S: Data<Elem = f64>,
1102{
1103 if x0.len() != 1 {
1105 return Err(OptimizeError::InvalidInput(
1106 "Scalar method requires exactly one variable".to_string(),
1107 ));
1108 }
1109
1110 let xtol = options.xtol.unwrap_or(1e-8);
1112 let ftol = options.ftol.unwrap_or(1e-8);
1113 let maxfev = options.maxfev.unwrap_or(100);
1114 let eps = options.eps.unwrap_or(1e-8);
1115
1116 let mut x = x0[0];
1118 let mut f_val = func(&[x])[0];
1119 let mut nfev = 1;
1120 let mut njev = 0;
1121
1122 let mut a = x - 1.0;
1124 let mut b = x + 1.0;
1125 let mut fa = func(&[a])[0];
1126 let mut fb = func(&[b])[0];
1127 nfev += 2;
1128
1129 let mut bracket_attempts = 0;
1131 while fa * fb > 0.0 && bracket_attempts < 10 {
1132 if fa.abs() < fb.abs() {
1133 a = a - 2.0 * (b - a);
1134 fa = func(&[a])[0];
1135 } else {
1136 b = b + 2.0 * (b - a);
1137 fb = func(&[b])[0];
1138 }
1139 nfev += 1;
1140 bracket_attempts += 1;
1141 }
1142
1143 let mut iter = 0;
1144 let mut converged = false;
1145
1146 while iter < maxfev {
1148 if f_val.abs() < ftol {
1150 converged = true;
1151 break;
1152 }
1153
1154 let df_dx = match &jacobian_fn {
1156 Some(jac_fn) => {
1157 let jac = jac_fn(&[x]);
1158 njev += 1;
1159 jac[[0, 0]]
1160 }
1161 None => {
1162 let f_plus = func(&[x + eps])[0];
1164 nfev += 1;
1165 (f_plus - f_val) / eps
1166 }
1167 };
1168
1169 let newton_step = if df_dx.abs() > 1e-14 {
1171 -f_val / df_dx
1172 } else {
1173 if fa * fb < 0.0 {
1175 if f_val * fa < 0.0 {
1176 (a - x) / 2.0
1177 } else {
1178 (b - x) / 2.0
1179 }
1180 } else {
1181 if f_val > 0.0 {
1183 -0.1
1184 } else {
1185 0.1
1186 }
1187 }
1188 };
1189
1190 let x_new = x + newton_step;
1191
1192 let x_new = if fa * fb < 0.0 {
1194 f64::max(a + 0.01 * (b - a), f64::min(b - 0.01 * (b - a), x_new))
1195 } else {
1196 x_new
1197 };
1198
1199 let f_new = func(&[x_new])[0];
1201 nfev += 1;
1202
1203 if (x_new - x).abs() < xtol * (1.0 + x.abs()) {
1205 converged = true;
1206 x = x_new;
1207 f_val = f_new;
1208 break;
1209 }
1210
1211 x = x_new;
1213 f_val = f_new;
1214
1215 if fa * fb < 0.0 {
1217 if f_val * fa < 0.0 {
1218 b = x;
1219 fb = f_val;
1220 } else {
1221 a = x;
1222 fa = f_val;
1223 }
1224 }
1225
1226 iter += 1;
1227 }
1228
1229 let mut result = OptimizeResults::default();
1231 result.x = Array1::from_vec(vec![x]);
1232 result.fun = f_val.powi(2);
1233 result.nfev = nfev;
1234 result.njev = njev;
1235 result.nit = iter;
1236 result.success = converged;
1237
1238 if converged {
1239 result.message = "Scalar root finding converged successfully".to_string();
1240 } else {
1241 result.message = "Maximum number of function evaluations reached".to_string();
1242 }
1243
1244 Ok(result)
1245}
1246
1247#[cfg(test)]
1248mod tests {
1249 use super::*;
1250 use scirs2_core::ndarray::array;
1251
1252 fn f(x: &[f64]) -> Array1<f64> {
1253 let x0 = x[0];
1254 let x1 = x[1];
1255 array![
1256 x0.powi(2) + x1.powi(2) - 1.0, x0 - x1 ]
1259 }
1260
1261 fn jacobian(x: &[f64]) -> Array2<f64> {
1262 let x0 = x[0];
1263 let x1 = x[1];
1264 array![[2.0 * x0, 2.0 * x1], [1.0, -1.0]]
1265 }
1266
1267 #[test]
1268 fn test_root_hybr() {
1269 let x0 = array![2.0, 2.0];
1270
1271 let result =
1272 root(f, &x0.view(), Method::Hybr, Some(jacobian), None).expect("Operation failed");
1273
1274 assert!(result.success);
1281
1282 let sol1 = (0.5f64).sqrt();
1284 let sol2 = -(0.5f64).sqrt();
1285
1286 let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
1287 let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
1288
1289 let min_dist = dist_to_sol1.min(dist_to_sol2);
1290 assert!(
1291 min_dist < 1e-5,
1292 "Distance to nearest solution: {}",
1293 min_dist
1294 );
1295
1296 assert!(result.fun < 1e-10);
1298
1299 assert!(result.jac.is_some());
1301
1302 println!(
1304 "Hybr root result: x = {:?}, f = {}, iterations = {}",
1305 result.x, result.fun, result.nit
1306 );
1307 }
1308
1309 #[test]
1310 fn test_root_broyden1() {
1311 let x0 = array![2.0, 2.0];
1312
1313 let result =
1314 root(f, &x0.view(), Method::Broyden1, Some(jacobian), None).expect("Operation failed");
1315
1316 assert!(result.success);
1317
1318 let sol1 = (0.5f64).sqrt();
1320 let sol2 = -(0.5f64).sqrt();
1321
1322 let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
1323 let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
1324
1325 let min_dist = dist_to_sol1.min(dist_to_sol2);
1326 assert!(
1327 min_dist < 1e-5,
1328 "Distance to nearest solution: {}",
1329 min_dist
1330 );
1331
1332 assert!(result.fun < 1e-10);
1334
1335 assert!(result.jac.is_some());
1337
1338 println!(
1340 "Broyden1 root result: x = {:?}, f = {}, iterations = {}",
1341 result.x, result.fun, result.nit
1342 );
1343 }
1344
1345 #[test]
1346 fn test_root_system() {
1347 fn system(x: &[f64]) -> Array1<f64> {
1353 let x0 = x[0];
1354 let x1 = x[1];
1355 let x2 = x[2];
1356 array![
1357 x0.powi(2) + x1.powi(2) + x2.powi(2) - 1.0, x0.powi(2) + x1.powi(2) - x2, x0 - x1 ]
1361 }
1362
1363 fn system_jac(x: &[f64]) -> Array2<f64> {
1364 let x0 = x[0];
1365 let x1 = x[1];
1366 array![
1367 [2.0 * x0, 2.0 * x1, 2.0 * x[2]],
1368 [2.0 * x0, 2.0 * x1, -1.0],
1369 [1.0, -1.0, 0.0]
1370 ]
1371 }
1372
1373 let x0 = array![0.5, 0.5, 0.5];
1375
1376 let options = Options {
1378 xtol: Some(1e-6),
1379 ftol: Some(1e-6),
1380 maxfev: Some(50),
1381 ..Options::default()
1382 };
1383
1384 let result = root(
1385 system,
1386 &x0.view(),
1387 Method::Hybr,
1388 Some(system_jac),
1389 Some(options.clone()),
1390 )
1391 .expect("Operation failed");
1392
1393 assert!(result.success);
1399
1400 let x = &result.x;
1402
1403 assert!((x[0] - x[1]).abs() < 1e-5);
1405
1406 assert!((x[0].powi(2) + x[1].powi(2) - x[2]).abs() < 1e-5);
1408
1409 assert!((x[0].powi(2) + x[1].powi(2) + x[2].powi(2) - 1.0).abs() < 1e-5);
1411
1412 assert!(result.fun < 1e-10);
1414
1415 println!(
1417 "Hybr system root: x = {:?}, f = {}, iterations = {}",
1418 result.x, result.fun, result.nit
1419 );
1420 }
1421
1422 #[test]
1423 fn test_compare_methods() {
1424 fn complex_system(x: &[f64]) -> Array1<f64> {
1429 let x0 = x[0];
1430 let x1 = x[1];
1431 array![
1432 x0.powi(2) + x1.powi(2) - 4.0, x1 - x0.powi(2) ]
1435 }
1436
1437 fn complex_system_jac(x: &[f64]) -> Array2<f64> {
1438 let x0 = x[0];
1439 let x1 = x[1];
1440 array![[2.0 * x0, 2.0 * x1], [-2.0 * x0, 1.0]]
1441 }
1442
1443 let x0 = array![0.0, 2.0];
1445
1446 let options = Options {
1448 xtol: Some(1e-6),
1449 ftol: Some(1e-6),
1450 maxfev: Some(100),
1451 ..Options::default()
1452 };
1453
1454 let hybr_result = root(
1456 complex_system,
1457 &x0.view(),
1458 Method::Hybr,
1459 Some(complex_system_jac),
1460 Some(options.clone()),
1461 )
1462 .expect("Operation failed");
1463
1464 let broyden1_result = root(
1465 complex_system,
1466 &x0.view(),
1467 Method::Broyden1,
1468 Some(complex_system_jac),
1469 Some(options.clone()),
1470 )
1471 .expect("Operation failed");
1472
1473 let broyden2_result = root(
1474 complex_system,
1475 &x0.view(),
1476 Method::Broyden2,
1477 Some(complex_system_jac),
1478 Some(options),
1479 )
1480 .expect("Operation failed");
1481
1482 assert!(hybr_result.success);
1484 assert!(broyden1_result.success);
1485 assert!(broyden2_result.success);
1486
1487 println!(
1489 "Hybr complex system: x = {:?}, f = {}, iterations = {}",
1490 hybr_result.x, hybr_result.fun, hybr_result.nit
1491 );
1492 println!(
1493 "Broyden1 complex system: x = {:?}, f = {}, iterations = {}",
1494 broyden1_result.x, broyden1_result.fun, broyden1_result.nit
1495 );
1496 println!(
1497 "Broyden2 complex system: x = {:?}, f = {}, iterations = {}",
1498 broyden2_result.x, broyden2_result.fun, broyden2_result.nit
1499 );
1500
1501 let distance12 = ((hybr_result.x[0] - broyden1_result.x[0]).powi(2)
1504 + (hybr_result.x[1] - broyden1_result.x[1]).powi(2))
1505 .sqrt();
1506
1507 let distance13 = ((hybr_result.x[0] - broyden2_result.x[0]).powi(2)
1508 + (hybr_result.x[1] - broyden2_result.x[1]).powi(2))
1509 .sqrt();
1510
1511 let distance23 = ((broyden1_result.x[0] - broyden2_result.x[0]).powi(2)
1512 + (broyden1_result.x[1] - broyden2_result.x[1]).powi(2))
1513 .sqrt();
1514
1515 assert!(
1517 distance12 < 1e-2,
1518 "Hybr and Broyden1 converged to different solutions, distance = {}",
1519 distance12
1520 );
1521 assert!(
1522 distance13 < 1e-2,
1523 "Hybr and Broyden2 converged to different solutions, distance = {}",
1524 distance13
1525 );
1526 assert!(
1527 distance23 < 1e-2,
1528 "Broyden1 and Broyden2 converged to different solutions, distance = {}",
1529 distance23
1530 );
1531
1532 let sol1_distance =
1535 ((hybr_result.x[0] - 2.0).powi(2) + (hybr_result.x[1] - 4.0).powi(2)).sqrt();
1536 let sol2_distance =
1537 ((hybr_result.x[0] + 2.0).powi(2) + (hybr_result.x[1] - 4.0).powi(2)).sqrt();
1538 let sol3_distance =
1539 ((hybr_result.x[0] - 0.0).powi(2) + (hybr_result.x[1] - 0.0).powi(2)).sqrt();
1540
1541 let closest_distance = sol1_distance.min(sol2_distance).min(sol3_distance);
1542 assert!(
1543 closest_distance < 2.0,
1544 "Hybr solution not close to any expected root"
1545 );
1546
1547 let broyden2_test = root(
1549 f,
1550 &array![2.0, 2.0].view(),
1551 Method::Broyden2,
1552 Some(jacobian),
1553 None,
1554 )
1555 .expect("Operation failed");
1556
1557 assert!(broyden2_test.success);
1558 assert!(broyden2_test.fun < 1e-10);
1559 println!(
1560 "Broyden2 simple test: x = {:?}, f = {}, iterations = {}",
1561 broyden2_test.x, broyden2_test.fun, broyden2_test.nit
1562 );
1563 }
1564
1565 #[test]
1566 fn test_anderson_method() {
1567 fn simple_f(x: &[f64]) -> Array1<f64> {
1570 array![x[0].powi(2) - 1.0]
1571 }
1572
1573 let x0 = array![2.0];
1574
1575 let options = Options {
1577 maxfev: Some(100),
1578 ftol: Some(1e-6),
1579 xtol: Some(1e-6),
1580 ..Options::default()
1581 };
1582
1583 let result = root(
1584 simple_f,
1585 &x0.view(),
1586 Method::Anderson,
1587 None::<fn(&[f64]) -> Array2<f64>>,
1588 Some(options),
1589 )
1590 .expect("Operation failed");
1591
1592 println!("Anderson method for simple problem:");
1593 println!(
1594 "Success: {}, x = {:?}, iterations = {}, fun = {}",
1595 result.success, result.x, result.nit, result.fun
1596 );
1597
1598 let dist = (result.x[0].abs() - 1.0).abs();
1600 println!("Distance to solution: {}", dist);
1601
1602 }
1604
1605 #[test]
1606 fn test_krylov_method() {
1607 let x0 = array![2.0, 2.0];
1609
1610 let options = Options {
1612 maxfev: Some(500),
1613 ftol: Some(1e-6),
1614 xtol: Some(1e-6),
1615 ..Options::default()
1616 };
1617
1618 let result = root(
1619 f,
1620 &x0.view(),
1621 Method::KrylovLevenbergMarquardt,
1622 None::<fn(&[f64]) -> Array2<f64>>,
1623 Some(options),
1624 )
1625 .expect("Operation failed");
1626
1627 println!(
1629 "Krylov method success: {}, x = {:?}, iterations = {}, fun = {}",
1630 result.success, result.x, result.nit, result.fun
1631 );
1632
1633 let sol1 = (0.5f64).sqrt();
1635 let sol2 = -(0.5f64).sqrt();
1636
1637 let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
1638 let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
1639
1640 let min_dist = dist_to_sol1.min(dist_to_sol2);
1641 println!("Krylov method distance to solution: {}", min_dist);
1643
1644 println!(
1646 "Krylov method result: x = {:?}, f = {}, iterations = {}",
1647 result.x, result.fun, result.nit
1648 );
1649 }
1650}