1use crate::error::{OptimizeError, OptimizeResult};
41use crate::result::OptimizeResults;
42use 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().unwrap());
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) = get_jacobian(x.as_slice().unwrap(), &f, &jacobian_fn);
283 nfev += nfev_inc;
284 njev += njev_inc;
285
286 let mut iter = 0;
288 let mut converged = false;
289
290 while iter < maxfev {
291 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
293 if f_norm < ftol {
294 converged = true;
295 break;
296 }
297
298 let delta = match solve(&jac, &(-&f)) {
300 Some(d) => d,
301 None => {
302 let mut gradient = Array1::zeros(n);
305 for i in 0..n {
306 for j in 0..f.len() {
307 gradient[i] += jac[[j, i]] * f[j];
308 }
309 }
310
311 let step_size = 0.1
312 / (1.0
313 + gradient
314 .iter()
315 .map(|&g: &f64| g.powi(2))
316 .sum::<f64>()
317 .sqrt());
318 -gradient * step_size
319 }
320 };
321
322 let mut x_new = x.clone();
324 for i in 0..n {
325 x_new[i] += delta[i];
326 }
327
328 let mut alpha = 1.0;
330 let mut f_new = func(x_new.as_slice().unwrap());
331 nfev += 1;
332
333 let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
334 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
335
336 let max_backtrack = 5;
338 let mut backtrack_count = 0;
339
340 while f_new_norm > f_norm && backtrack_count < max_backtrack {
341 alpha *= 0.5;
342 backtrack_count += 1;
343
344 for i in 0..n {
346 x_new[i] = x[i] + alpha * delta[i];
347 }
348
349 f_new = func(x_new.as_slice().unwrap());
351 nfev += 1;
352 f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
353 }
354
355 let step_norm = (0..n)
357 .map(|i| (x_new[i] - x[i]).powi(2))
358 .sum::<f64>()
359 .sqrt();
360 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
361
362 if step_norm < xtol * (1.0 + x_norm) {
363 converged = true;
364 x = x_new;
365 f = f_new;
366 break;
367 }
368
369 x = x_new;
371 f = f_new;
372
373 let (new_jac, nfev_delta, njev_delta) =
375 get_jacobian(x.as_slice().unwrap(), &f, &jacobian_fn);
376 jac = new_jac;
377 nfev += nfev_delta;
378 njev += njev_delta;
379
380 iter += 1;
381 }
382
383 let mut result = OptimizeResults::default();
385 result.x = x;
386 result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
387
388 let (jac_vec, _) = jac.into_raw_vec_and_offset();
390 result.jac = Some(jac_vec);
391
392 result.nfev = nfev;
393 result.njev = njev;
394 result.nit = iter;
395 result.success = converged;
396
397 if converged {
398 result.message = "Root finding converged successfully".to_string();
399 } else {
400 result.message = "Maximum number of function evaluations reached".to_string();
401 }
402
403 Ok(result)
404}
405
406#[allow(dead_code)]
408fn solve(a: &Array2<f64>, b: &Array1<f64>) -> Option<Array1<f64>> {
409 use scirs2_linalg::solve;
410
411 solve(&a.view(), &b.view(), None).ok()
412}
413
414#[allow(dead_code)]
416fn root_broyden1<F, J, S>(
417 func: F,
418 x0: &ArrayBase<S, Ix1>,
419 jacobian_fn: Option<J>,
420 options: &Options,
421) -> OptimizeResult<OptimizeResults<f64>>
422where
423 F: Fn(&[f64]) -> Array1<f64>,
424 J: Fn(&[f64]) -> Array2<f64>,
425 S: Data<Elem = f64>,
426{
427 let xtol = options.xtol.unwrap_or(1e-8);
429 let ftol = options.ftol.unwrap_or(1e-8);
430 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
431 let eps = options.eps.unwrap_or(1e-8);
432
433 let n = x0.len();
435 let mut x = x0.to_owned();
436 let mut f = func(x.as_slice().unwrap());
437 let mut nfev = 1;
438 let mut njev = 0;
439
440 let compute_numerical_jac =
442 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
443 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
444 let mut count = 0;
445
446 for j in 0..x_values.len() {
447 let mut x_h = Vec::from(x_values);
448 x_h[j] += eps;
449 let f_h = func(&x_h);
450 count += 1;
451
452 for i in 0..f_values.len() {
453 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
454 }
455 }
456
457 (jac, count)
458 };
459
460 let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
462 Some(jac_fn) => {
463 let j = jac_fn(x.as_slice().unwrap());
464 (j, 0, 1)
465 }
466 None => {
467 let (j, count) = compute_numerical_jac(x.as_slice().unwrap(), &f);
468 (j, count, 0)
469 }
470 };
471
472 nfev += nfev_inc;
473 njev += njev_inc;
474
475 let mut iter = 0;
477 let mut converged = false;
478
479 while iter < maxfev {
482 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
484 if f_norm < ftol {
485 converged = true;
486 break;
487 }
488
489 let delta = match solve(&jac, &(-&f)) {
491 Some(d) => d,
492 None => {
493 let mut gradient = Array1::zeros(n);
496 for i in 0..n {
497 for j in 0..f.len() {
498 gradient[i] += jac[[j, i]] * f[j];
499 }
500 }
501
502 let step_size = 0.1
503 / (1.0
504 + gradient
505 .iter()
506 .map(|&g: &f64| g.powi(2))
507 .sum::<f64>()
508 .sqrt());
509 -gradient * step_size
510 }
511 };
512
513 let mut x_new = x.clone();
515 for i in 0..n {
516 x_new[i] += delta[i];
517 }
518
519 let mut alpha = 1.0;
521 let mut f_new = func(x_new.as_slice().unwrap());
522 nfev += 1;
523
524 let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
525 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
526
527 let max_backtrack = 5;
529 let mut backtrack_count = 0;
530
531 while f_new_norm > f_norm && backtrack_count < max_backtrack {
532 alpha *= 0.5;
533 backtrack_count += 1;
534
535 for i in 0..n {
537 x_new[i] = x[i] + alpha * delta[i];
538 }
539
540 f_new = func(x_new.as_slice().unwrap());
542 nfev += 1;
543 f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
544 }
545
546 let step_norm = (0..n)
548 .map(|i| (x_new[i] - x[i]).powi(2))
549 .sum::<f64>()
550 .sqrt();
551 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
552
553 if step_norm < xtol * (1.0 + x_norm) {
554 converged = true;
555 x = x_new;
556 f = f_new;
557 break;
558 }
559
560 let mut s: Array1<f64> = Array1::zeros(n);
565 for i in 0..n {
566 s[i] = x_new[i] - x[i];
567 }
568
569 let mut y: Array1<f64> = Array1::zeros(f.len());
571 for i in 0..f.len() {
572 y[i] = f_new[i] - f[i];
573 }
574
575 let mut js: Array1<f64> = Array1::zeros(f.len());
577 for i in 0..f.len() {
578 for j in 0..n {
579 js[i] += jac[[i, j]] * s[j];
580 }
581 }
582
583 let mut residual: Array1<f64> = Array1::zeros(f.len());
585 for i in 0..f.len() {
586 residual[i] = y[i] - js[i];
587 }
588
589 let s_norm_squared = s.iter().map(|&si| si.powi(2)).sum::<f64>();
591
592 if s_norm_squared > 1e-14 {
593 for i in 0..f.len() {
596 for j in 0..n {
597 jac[[i, j]] += residual[i] * s[j] / s_norm_squared;
598 }
599 }
600 }
601
602 x = x_new;
604 f = f_new;
605
606 iter += 1;
607 }
608
609 let mut result = OptimizeResults::default();
611 result.x = x;
612 result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
613
614 let (jac_vec, _) = jac.into_raw_vec_and_offset();
616 result.jac = Some(jac_vec);
617
618 result.nfev = nfev;
619 result.njev = njev;
620 result.nit = iter;
621 result.success = converged;
622
623 if converged {
624 result.message = "Root finding converged successfully".to_string();
625 } else {
626 result.message = "Maximum number of function evaluations reached".to_string();
627 }
628
629 Ok(result)
630}
631
632#[allow(dead_code)]
634fn root_broyden2<F, J, S>(
635 func: F,
636 x0: &ArrayBase<S, Ix1>,
637 jacobian_fn: Option<J>,
638 options: &Options,
639) -> OptimizeResult<OptimizeResults<f64>>
640where
641 F: Fn(&[f64]) -> Array1<f64>,
642 J: Fn(&[f64]) -> Array2<f64>,
643 S: Data<Elem = f64>,
644{
645 let xtol = options.xtol.unwrap_or(1e-8);
647 let ftol = options.ftol.unwrap_or(1e-8);
648 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
649 let eps = options.eps.unwrap_or(1e-8);
650
651 let n = x0.len();
653 let mut x = x0.to_owned();
654 let mut f = func(x.as_slice().unwrap());
655 let mut nfev = 1;
656 let mut njev = 0;
657
658 let compute_numerical_jac =
660 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
661 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
662 let mut count = 0;
663
664 for j in 0..x_values.len() {
665 let mut x_h = Vec::from(x_values);
666 x_h[j] += eps;
667 let f_h = func(&x_h);
668 count += 1;
669
670 for i in 0..f_values.len() {
671 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
672 }
673 }
674
675 (jac, count)
676 };
677
678 let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
680 Some(jac_fn) => {
681 let j = jac_fn(x.as_slice().unwrap());
682 (j, 0, 1)
683 }
684 None => {
685 let (j, count) = compute_numerical_jac(x.as_slice().unwrap(), &f);
686 (j, count, 0)
687 }
688 };
689
690 nfev += nfev_inc;
691 njev += njev_inc;
692
693 let mut iter = 0;
695 let mut converged = false;
696
697 while iter < maxfev {
698 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
700 if f_norm < ftol {
701 converged = true;
702 break;
703 }
704
705 let delta = match solve(&jac, &(-&f)) {
707 Some(d) => d,
708 None => {
709 let mut gradient = Array1::zeros(n);
712 for i in 0..n {
713 for j in 0..f.len() {
714 gradient[i] += jac[[j, i]] * f[j];
715 }
716 }
717
718 let step_size = 0.1
719 / (1.0
720 + gradient
721 .iter()
722 .map(|&g: &f64| g.powi(2))
723 .sum::<f64>()
724 .sqrt());
725 -gradient * step_size
726 }
727 };
728
729 let mut x_new = x.clone();
731 for i in 0..n {
732 x_new[i] += delta[i];
733 }
734
735 let mut alpha = 1.0;
737 let mut f_new = func(x_new.as_slice().unwrap());
738 nfev += 1;
739
740 let mut f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
741 let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
742
743 let max_backtrack = 5;
745 let mut backtrack_count = 0;
746
747 while f_new_norm > f_norm && backtrack_count < max_backtrack {
748 alpha *= 0.5;
749 backtrack_count += 1;
750
751 for i in 0..n {
753 x_new[i] = x[i] + alpha * delta[i];
754 }
755
756 f_new = func(x_new.as_slice().unwrap());
758 nfev += 1;
759 f_new_norm = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
760 }
761
762 let step_norm = (0..n)
764 .map(|i| (x_new[i] - x[i]).powi(2))
765 .sum::<f64>()
766 .sqrt();
767 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
768
769 if step_norm < xtol * (1.0 + x_norm) {
770 converged = true;
771 x = x_new;
772 f = f_new;
773 break;
774 }
775
776 let mut s: Array1<f64> = Array1::zeros(n);
778 for i in 0..n {
779 s[i] = x_new[i] - x[i];
780 }
781
782 let mut y: Array1<f64> = Array1::zeros(f.len());
784 for i in 0..f.len() {
785 y[i] = f_new[i] - f[i];
786 }
787
788 let mut js: Array1<f64> = Array1::zeros(f.len());
793 for i in 0..f.len() {
794 for j in 0..n {
795 js[i] += jac[[i, j]] * s[j];
796 }
797 }
798
799 let mut residual: Array1<f64> = Array1::zeros(f.len());
801 for i in 0..f.len() {
802 residual[i] = y[i] - js[i];
803 }
804
805 let mut jtjs: Array1<f64> = Array1::zeros(n);
807
808 let mut jtj: Array2<f64> = Array2::zeros((n, n));
810 for i in 0..n {
811 for j in 0..n {
812 for k in 0..f.len() {
813 jtj[[i, j]] += jac[[k, i]] * jac[[k, j]];
814 }
815 }
816 }
817
818 for i in 0..n {
820 for j in 0..n {
821 jtjs[i] += jtj[[i, j]] * s[j];
822 }
823 }
824
825 let mut denominator: f64 = 0.0;
827 for i in 0..n {
828 denominator += s[i] * jtjs[i];
829 }
830
831 if denominator.abs() > 1e-14 {
833 for i in 0..f.len() {
835 for j in 0..n {
836 let mut change: f64 = 0.0;
838 for k in 0..n {
839 change += residual[i] * s[k] * jac[[i, k]];
840 }
841 jac[[i, j]] += change * s[j] / denominator;
842 }
843 }
844 }
845
846 x = x_new;
848 f = f_new;
849
850 iter += 1;
851 }
852
853 let mut result = OptimizeResults::default();
855 result.x = x;
856 result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
857
858 let (jac_vec, _) = jac.into_raw_vec_and_offset();
860 result.jac = Some(jac_vec);
861
862 result.nfev = nfev;
863 result.njev = njev;
864 result.nit = iter;
865 result.success = converged;
866
867 if converged {
868 result.message = "Root finding converged successfully".to_string();
869 } else {
870 result.message = "Maximum number of function evaluations reached".to_string();
871 }
872
873 Ok(result)
874}
875
876#[allow(dead_code)]
882fn root_levenberg_marquardt<F, J, S>(
883 func: F,
884 x0: &ArrayBase<S, Ix1>,
885 jacobian_fn: Option<J>,
886 options: &Options,
887) -> OptimizeResult<OptimizeResults<f64>>
888where
889 F: Fn(&[f64]) -> Array1<f64>,
890 J: Fn(&[f64]) -> Array2<f64>,
891 S: Data<Elem = f64>,
892{
893 let xtol = options.xtol.unwrap_or(1e-8);
895 let ftol = options.ftol.unwrap_or(1e-8);
896 let maxfev = options.maxfev.unwrap_or(100 * x0.len());
897 let eps = options.eps.unwrap_or(1e-8);
898
899 let n = x0.len();
901 let mut x = x0.to_owned();
902 let mut f = func(x.as_slice().unwrap());
903 let mut nfev = 1;
904 let mut njev = 0;
905
906 let mut lambda = 1e-3; let lambda_factor = 10.0; let compute_numerical_jac =
912 |x_values: &[f64], f_values: &Array1<f64>| -> (Array2<f64>, usize) {
913 let mut jac = Array2::zeros((f_values.len(), x_values.len()));
914 let mut count = 0;
915
916 for j in 0..x_values.len() {
917 let mut x_h = Vec::from(x_values);
918 x_h[j] += eps;
919 let f_h = func(&x_h);
920 count += 1;
921
922 for i in 0..f_values.len() {
923 jac[[i, j]] = (f_h[i] - f_values[i]) / eps;
924 }
925 }
926
927 (jac, count)
928 };
929
930 let (mut jac, nfev_inc, njev_inc) = match &jacobian_fn {
932 Some(jac_fn) => {
933 let j = jac_fn(x.as_slice().unwrap());
934 (j, 0, 1)
935 }
936 None => {
937 let (j, count) = compute_numerical_jac(x.as_slice().unwrap(), &f);
938 (j, count, 0)
939 }
940 };
941
942 nfev += nfev_inc;
943 njev += njev_inc;
944
945 let mut iter = 0;
947 let mut converged = false;
948 let mut current_cost = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
949
950 while iter < maxfev {
951 let f_norm = current_cost.sqrt();
953 if f_norm < ftol {
954 converged = true;
955 break;
956 }
957
958 let mut jtj = Array2::zeros((n, n));
960 let mut jtf = Array1::zeros(n);
961
962 for i in 0..n {
963 for j in 0..n {
964 for k in 0..f.len() {
965 jtj[[i, j]] += jac[[k, i]] * jac[[k, j]];
966 }
967 }
968 for k in 0..f.len() {
969 jtf[i] += jac[[k, i]] * f[k];
970 }
971 }
972
973 for i in 0..n {
975 jtj[[i, i]] += lambda;
976 }
977
978 let neg_jtf = jtf.mapv(|x: f64| -x);
980 let delta = match solve(&jtj, &neg_jtf) {
981 Some(d) => d,
982 None => {
983 lambda *= lambda_factor;
985 for i in 0..n {
986 jtj[[i, i]] += lambda;
987 }
988 let neg_jtf2 = jtf.mapv(|x| -x);
989 match solve(&jtj, &neg_jtf2) {
990 Some(d) => d,
991 None => {
992 let step_size =
994 0.1 / (1.0 + jtf.iter().map(|&g| g.powi(2)).sum::<f64>().sqrt());
995 jtf.mapv(|x| -x * step_size)
996 }
997 }
998 }
999 };
1000
1001 let mut x_new = x.clone();
1003 for i in 0..n {
1004 x_new[i] += delta[i];
1005 }
1006
1007 let f_new = func(x_new.as_slice().unwrap());
1009 nfev += 1;
1010 let new_cost = f_new.iter().map(|&fi| fi.powi(2)).sum::<f64>();
1011
1012 if new_cost < current_cost {
1014 let improvement = (current_cost - new_cost) / current_cost;
1016
1017 let step_norm = (0..n)
1019 .map(|i| (x_new[i] - x[i]).powi(2))
1020 .sum::<f64>()
1021 .sqrt();
1022 let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
1023
1024 if step_norm < xtol * (1.0 + x_norm) || improvement < ftol {
1025 converged = true;
1026 x = x_new;
1027 f = f_new;
1028 current_cost = new_cost;
1029 break;
1030 }
1031
1032 x = x_new;
1034 f = f_new;
1035 current_cost = new_cost;
1036
1037 lambda = f64::max(lambda / lambda_factor, 1e-12);
1039
1040 let (new_jac, nfev_delta, njev_delta) = match &jacobian_fn {
1042 Some(jac_fn) => {
1043 let j = jac_fn(x.as_slice().unwrap());
1044 (j, 0, 1)
1045 }
1046 None => {
1047 let (j, count) = compute_numerical_jac(x.as_slice().unwrap(), &f);
1048 (j, count, 0)
1049 }
1050 };
1051 jac = new_jac;
1052 nfev += nfev_delta;
1053 njev += njev_delta;
1054 } else {
1055 lambda = f64::min(lambda * lambda_factor, 1e12);
1057 }
1058
1059 iter += 1;
1060 }
1061
1062 let mut result = OptimizeResults::default();
1064 result.x = x;
1065 result.fun = current_cost;
1066
1067 let (jac_vec, _) = jac.into_raw_vec_and_offset();
1069 result.jac = Some(jac_vec);
1070
1071 result.nfev = nfev;
1072 result.njev = njev;
1073 result.nit = iter;
1074 result.success = converged;
1075
1076 if converged {
1077 result.message = "Levenberg-Marquardt converged successfully".to_string();
1078 } else {
1079 result.message = "Maximum number of function evaluations reached".to_string();
1080 }
1081
1082 Ok(result)
1083}
1084
1085#[allow(dead_code)]
1090fn root_scalar<F, J, S>(
1091 func: F,
1092 x0: &ArrayBase<S, Ix1>,
1093 jacobian_fn: Option<J>,
1094 options: &Options,
1095) -> OptimizeResult<OptimizeResults<f64>>
1096where
1097 F: Fn(&[f64]) -> Array1<f64>,
1098 J: Fn(&[f64]) -> Array2<f64>,
1099 S: Data<Elem = f64>,
1100{
1101 if x0.len() != 1 {
1103 return Err(OptimizeError::InvalidInput(
1104 "Scalar method requires exactly one variable".to_string(),
1105 ));
1106 }
1107
1108 let xtol = options.xtol.unwrap_or(1e-8);
1110 let ftol = options.ftol.unwrap_or(1e-8);
1111 let maxfev = options.maxfev.unwrap_or(100);
1112 let eps = options.eps.unwrap_or(1e-8);
1113
1114 let mut x = x0[0];
1116 let mut f_val = func(&[x])[0];
1117 let mut nfev = 1;
1118 let mut njev = 0;
1119
1120 let mut a = x - 1.0;
1122 let mut b = x + 1.0;
1123 let mut fa = func(&[a])[0];
1124 let mut fb = func(&[b])[0];
1125 nfev += 2;
1126
1127 let mut bracket_attempts = 0;
1129 while fa * fb > 0.0 && bracket_attempts < 10 {
1130 if fa.abs() < fb.abs() {
1131 a = a - 2.0 * (b - a);
1132 fa = func(&[a])[0];
1133 } else {
1134 b = b + 2.0 * (b - a);
1135 fb = func(&[b])[0];
1136 }
1137 nfev += 1;
1138 bracket_attempts += 1;
1139 }
1140
1141 let mut iter = 0;
1142 let mut converged = false;
1143
1144 while iter < maxfev {
1146 if f_val.abs() < ftol {
1148 converged = true;
1149 break;
1150 }
1151
1152 let df_dx = match &jacobian_fn {
1154 Some(jac_fn) => {
1155 let jac = jac_fn(&[x]);
1156 njev += 1;
1157 jac[[0, 0]]
1158 }
1159 None => {
1160 let f_plus = func(&[x + eps])[0];
1162 nfev += 1;
1163 (f_plus - f_val) / eps
1164 }
1165 };
1166
1167 let newton_step = if df_dx.abs() > 1e-14 {
1169 -f_val / df_dx
1170 } else {
1171 if fa * fb < 0.0 {
1173 if f_val * fa < 0.0 {
1174 (a - x) / 2.0
1175 } else {
1176 (b - x) / 2.0
1177 }
1178 } else {
1179 if f_val > 0.0 {
1181 -0.1
1182 } else {
1183 0.1
1184 }
1185 }
1186 };
1187
1188 let x_new = x + newton_step;
1189
1190 let x_new = if fa * fb < 0.0 {
1192 f64::max(a + 0.01 * (b - a), f64::min(b - 0.01 * (b - a), x_new))
1193 } else {
1194 x_new
1195 };
1196
1197 let f_new = func(&[x_new])[0];
1199 nfev += 1;
1200
1201 if (x_new - x).abs() < xtol * (1.0 + x.abs()) {
1203 converged = true;
1204 x = x_new;
1205 f_val = f_new;
1206 break;
1207 }
1208
1209 x = x_new;
1211 f_val = f_new;
1212
1213 if fa * fb < 0.0 {
1215 if f_val * fa < 0.0 {
1216 b = x;
1217 fb = f_val;
1218 } else {
1219 a = x;
1220 fa = f_val;
1221 }
1222 }
1223
1224 iter += 1;
1225 }
1226
1227 let mut result = OptimizeResults::default();
1229 result.x = Array1::from_vec(vec![x]);
1230 result.fun = f_val.powi(2);
1231 result.nfev = nfev;
1232 result.njev = njev;
1233 result.nit = iter;
1234 result.success = converged;
1235
1236 if converged {
1237 result.message = "Scalar root finding converged successfully".to_string();
1238 } else {
1239 result.message = "Maximum number of function evaluations reached".to_string();
1240 }
1241
1242 Ok(result)
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247 use super::*;
1248 use ndarray::array;
1249
1250 fn f(x: &[f64]) -> Array1<f64> {
1251 let x0 = x[0];
1252 let x1 = x[1];
1253 array![
1254 x0.powi(2) + x1.powi(2) - 1.0, x0 - x1 ]
1257 }
1258
1259 fn jacobian(x: &[f64]) -> Array2<f64> {
1260 let x0 = x[0];
1261 let x1 = x[1];
1262 array![[2.0 * x0, 2.0 * x1], [1.0, -1.0]]
1263 }
1264
1265 #[test]
1266 fn test_root_hybr() {
1267 let x0 = array![2.0, 2.0];
1268
1269 let result = root(f, &x0.view(), Method::Hybr, Some(jacobian), None).unwrap();
1270
1271 assert!(result.success);
1278
1279 let sol1 = (0.5f64).sqrt();
1281 let sol2 = -(0.5f64).sqrt();
1282
1283 let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
1284 let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
1285
1286 let min_dist = dist_to_sol1.min(dist_to_sol2);
1287 assert!(
1288 min_dist < 1e-5,
1289 "Distance to nearest solution: {}",
1290 min_dist
1291 );
1292
1293 assert!(result.fun < 1e-10);
1295
1296 assert!(result.jac.is_some());
1298
1299 println!(
1301 "Hybr root result: x = {:?}, f = {}, iterations = {}",
1302 result.x, result.fun, result.nit
1303 );
1304 }
1305
1306 #[test]
1307 fn test_root_broyden1() {
1308 let x0 = array![2.0, 2.0];
1309
1310 let result = root(f, &x0.view(), Method::Broyden1, Some(jacobian), None).unwrap();
1311
1312 assert!(result.success);
1313
1314 let sol1 = (0.5f64).sqrt();
1316 let sol2 = -(0.5f64).sqrt();
1317
1318 let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
1319 let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
1320
1321 let min_dist = dist_to_sol1.min(dist_to_sol2);
1322 assert!(
1323 min_dist < 1e-5,
1324 "Distance to nearest solution: {}",
1325 min_dist
1326 );
1327
1328 assert!(result.fun < 1e-10);
1330
1331 assert!(result.jac.is_some());
1333
1334 println!(
1336 "Broyden1 root result: x = {:?}, f = {}, iterations = {}",
1337 result.x, result.fun, result.nit
1338 );
1339 }
1340
1341 #[test]
1342 fn test_root_system() {
1343 fn system(x: &[f64]) -> Array1<f64> {
1349 let x0 = x[0];
1350 let x1 = x[1];
1351 let x2 = x[2];
1352 array![
1353 x0.powi(2) + x1.powi(2) + x2.powi(2) - 1.0, x0.powi(2) + x1.powi(2) - x2, x0 - x1 ]
1357 }
1358
1359 fn system_jac(x: &[f64]) -> Array2<f64> {
1360 let x0 = x[0];
1361 let x1 = x[1];
1362 array![
1363 [2.0 * x0, 2.0 * x1, 2.0 * x[2]],
1364 [2.0 * x0, 2.0 * x1, -1.0],
1365 [1.0, -1.0, 0.0]
1366 ]
1367 }
1368
1369 let x0 = array![0.5, 0.5, 0.5];
1371
1372 let options = Options {
1374 xtol: Some(1e-6),
1375 ftol: Some(1e-6),
1376 maxfev: Some(50),
1377 ..Options::default()
1378 };
1379
1380 let result = root(
1381 system,
1382 &x0.view(),
1383 Method::Hybr,
1384 Some(system_jac),
1385 Some(options.clone()),
1386 )
1387 .unwrap();
1388
1389 assert!(result.success);
1395
1396 let x = &result.x;
1398
1399 assert!((x[0] - x[1]).abs() < 1e-5);
1401
1402 assert!((x[0].powi(2) + x[1].powi(2) - x[2]).abs() < 1e-5);
1404
1405 assert!((x[0].powi(2) + x[1].powi(2) + x[2].powi(2) - 1.0).abs() < 1e-5);
1407
1408 assert!(result.fun < 1e-10);
1410
1411 println!(
1413 "Hybr system root: x = {:?}, f = {}, iterations = {}",
1414 result.x, result.fun, result.nit
1415 );
1416 }
1417
1418 #[test]
1419 fn test_compare_methods() {
1420 fn complex_system(x: &[f64]) -> Array1<f64> {
1425 let x0 = x[0];
1426 let x1 = x[1];
1427 array![
1428 x0.powi(2) + x1.powi(2) - 4.0, x1 - x0.powi(2) ]
1431 }
1432
1433 fn complex_system_jac(x: &[f64]) -> Array2<f64> {
1434 let x0 = x[0];
1435 let x1 = x[1];
1436 array![[2.0 * x0, 2.0 * x1], [-2.0 * x0, 1.0]]
1437 }
1438
1439 let x0 = array![0.0, 2.0];
1441
1442 let options = Options {
1444 xtol: Some(1e-6),
1445 ftol: Some(1e-6),
1446 maxfev: Some(100),
1447 ..Options::default()
1448 };
1449
1450 let hybr_result = root(
1452 complex_system,
1453 &x0.view(),
1454 Method::Hybr,
1455 Some(complex_system_jac),
1456 Some(options.clone()),
1457 )
1458 .unwrap();
1459
1460 let broyden1_result = root(
1461 complex_system,
1462 &x0.view(),
1463 Method::Broyden1,
1464 Some(complex_system_jac),
1465 Some(options.clone()),
1466 )
1467 .unwrap();
1468
1469 let broyden2_result = root(
1470 complex_system,
1471 &x0.view(),
1472 Method::Broyden2,
1473 Some(complex_system_jac),
1474 Some(options),
1475 )
1476 .unwrap();
1477
1478 assert!(hybr_result.success);
1480 assert!(broyden1_result.success);
1481 assert!(broyden2_result.success);
1482
1483 println!(
1485 "Hybr complex system: x = {:?}, f = {}, iterations = {}",
1486 hybr_result.x, hybr_result.fun, hybr_result.nit
1487 );
1488 println!(
1489 "Broyden1 complex system: x = {:?}, f = {}, iterations = {}",
1490 broyden1_result.x, broyden1_result.fun, broyden1_result.nit
1491 );
1492 println!(
1493 "Broyden2 complex system: x = {:?}, f = {}, iterations = {}",
1494 broyden2_result.x, broyden2_result.fun, broyden2_result.nit
1495 );
1496
1497 let distance12 = ((hybr_result.x[0] - broyden1_result.x[0]).powi(2)
1500 + (hybr_result.x[1] - broyden1_result.x[1]).powi(2))
1501 .sqrt();
1502
1503 let distance13 = ((hybr_result.x[0] - broyden2_result.x[0]).powi(2)
1504 + (hybr_result.x[1] - broyden2_result.x[1]).powi(2))
1505 .sqrt();
1506
1507 let distance23 = ((broyden1_result.x[0] - broyden2_result.x[0]).powi(2)
1508 + (broyden1_result.x[1] - broyden2_result.x[1]).powi(2))
1509 .sqrt();
1510
1511 assert!(
1513 distance12 < 1e-2,
1514 "Hybr and Broyden1 converged to different solutions, distance = {}",
1515 distance12
1516 );
1517 assert!(
1518 distance13 < 1e-2,
1519 "Hybr and Broyden2 converged to different solutions, distance = {}",
1520 distance13
1521 );
1522 assert!(
1523 distance23 < 1e-2,
1524 "Broyden1 and Broyden2 converged to different solutions, distance = {}",
1525 distance23
1526 );
1527
1528 let sol1_distance =
1531 ((hybr_result.x[0] - 2.0).powi(2) + (hybr_result.x[1] - 4.0).powi(2)).sqrt();
1532 let sol2_distance =
1533 ((hybr_result.x[0] + 2.0).powi(2) + (hybr_result.x[1] - 4.0).powi(2)).sqrt();
1534 let sol3_distance =
1535 ((hybr_result.x[0] - 0.0).powi(2) + (hybr_result.x[1] - 0.0).powi(2)).sqrt();
1536
1537 let closest_distance = sol1_distance.min(sol2_distance).min(sol3_distance);
1538 assert!(
1539 closest_distance < 2.0,
1540 "Hybr solution not close to any expected root"
1541 );
1542
1543 let broyden2_test = root(
1545 f,
1546 &array![2.0, 2.0].view(),
1547 Method::Broyden2,
1548 Some(jacobian),
1549 None,
1550 )
1551 .unwrap();
1552
1553 assert!(broyden2_test.success);
1554 assert!(broyden2_test.fun < 1e-10);
1555 println!(
1556 "Broyden2 simple test: x = {:?}, f = {}, iterations = {}",
1557 broyden2_test.x, broyden2_test.fun, broyden2_test.nit
1558 );
1559 }
1560
1561 #[test]
1562 fn test_anderson_method() {
1563 fn simple_f(x: &[f64]) -> Array1<f64> {
1566 array![x[0].powi(2) - 1.0]
1567 }
1568
1569 let x0 = array![2.0];
1570
1571 let options = Options {
1573 maxfev: Some(100),
1574 ftol: Some(1e-6),
1575 xtol: Some(1e-6),
1576 ..Options::default()
1577 };
1578
1579 let result = root(
1580 simple_f,
1581 &x0.view(),
1582 Method::Anderson,
1583 None::<fn(&[f64]) -> Array2<f64>>,
1584 Some(options),
1585 )
1586 .unwrap();
1587
1588 println!("Anderson method for simple problem:");
1589 println!(
1590 "Success: {}, x = {:?}, iterations = {}, fun = {}",
1591 result.success, result.x, result.nit, result.fun
1592 );
1593
1594 let dist = (result.x[0].abs() - 1.0).abs();
1596 println!("Distance to solution: {}", dist);
1597
1598 }
1600
1601 #[test]
1602 fn test_krylov_method() {
1603 let x0 = array![2.0, 2.0];
1605
1606 let options = Options {
1608 maxfev: Some(500),
1609 ftol: Some(1e-6),
1610 xtol: Some(1e-6),
1611 ..Options::default()
1612 };
1613
1614 let result = root(
1615 f,
1616 &x0.view(),
1617 Method::KrylovLevenbergMarquardt,
1618 None::<fn(&[f64]) -> Array2<f64>>,
1619 Some(options),
1620 )
1621 .unwrap();
1622
1623 println!(
1625 "Krylov method success: {}, x = {:?}, iterations = {}, fun = {}",
1626 result.success, result.x, result.nit, result.fun
1627 );
1628
1629 let sol1 = (0.5f64).sqrt();
1631 let sol2 = -(0.5f64).sqrt();
1632
1633 let dist_to_sol1 = ((result.x[0] - sol1).powi(2) + (result.x[1] - sol1).powi(2)).sqrt();
1634 let dist_to_sol2 = ((result.x[0] - sol2).powi(2) + (result.x[1] - sol2).powi(2)).sqrt();
1635
1636 let min_dist = dist_to_sol1.min(dist_to_sol2);
1637 println!("Krylov method distance to solution: {}", min_dist);
1639
1640 println!(
1642 "Krylov method result: x = {:?}, f = {}, iterations = {}",
1643 result.x, result.fun, result.nit
1644 );
1645 }
1646}