1use crate::error::OptimizeResult;
41use crate::result::OptimizeResults;
42use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Data, Ix1};
43use std::fmt;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Method {
48 TrustRegionReflective,
50
51 LevenbergMarquardt,
53
54 Dogbox,
56}
57
58impl fmt::Display for Method {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 match self {
61 Method::TrustRegionReflective => write!(f, "trf"),
62 Method::LevenbergMarquardt => write!(f, "lm"),
63 Method::Dogbox => write!(f, "dogbox"),
64 }
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct Options {
71 pub max_nfev: Option<usize>,
73
74 pub xtol: Option<f64>,
76
77 pub ftol: Option<f64>,
79
80 pub gtol: Option<f64>,
82
83 pub verbose: usize,
85
86 pub diff_step: Option<f64>,
88
89 pub use_finite_diff: bool,
91}
92
93impl Default for Options {
94 fn default() -> Self {
95 Options {
96 max_nfev: None,
97 xtol: Some(1e-8),
98 ftol: Some(1e-8),
99 gtol: Some(1e-8),
100 verbose: 0,
101 diff_step: None,
102 use_finite_diff: false,
103 }
104 }
105}
106
107#[allow(dead_code)]
159pub fn least_squares<F, J, D, S1, S2>(
160 residuals: F,
161 x0: &ArrayBase<S1, Ix1>,
162 method: Method,
163 jacobian: Option<J>,
164 data: &ArrayBase<S2, Ix1>,
165 options: Option<Options>,
166) -> OptimizeResult<OptimizeResults<f64>>
167where
168 F: Fn(&[f64], &[D]) -> Array1<f64>,
169 J: Fn(&[f64], &[D]) -> Array2<f64>,
170 D: Clone,
171 S1: Data<Elem = f64>,
172 S2: Data<Elem = D>,
173{
174 let options = options.unwrap_or_default();
175
176 match method {
178 Method::LevenbergMarquardt => least_squares_lm(residuals, x0, jacobian, data, &options),
179 Method::TrustRegionReflective => least_squares_trf(residuals, x0, jacobian, data, &options),
180 Method::Dogbox => least_squares_dogbox(residuals, x0, jacobian, data, &options),
181 }
182}
183
184#[allow(dead_code)]
186fn least_squares_lm<F, J, D, S1, S2>(
187 residuals: F,
188 x0: &ArrayBase<S1, Ix1>,
189 jacobian: Option<J>,
190 data: &ArrayBase<S2, Ix1>,
191 options: &Options,
192) -> OptimizeResult<OptimizeResults<f64>>
193where
194 F: Fn(&[f64], &[D]) -> Array1<f64>,
195 J: Fn(&[f64], &[D]) -> Array2<f64>,
196 D: Clone,
197 S1: Data<Elem = f64>,
198 S2: Data<Elem = D>,
199{
200 let ftol = options.ftol.unwrap_or(1e-8);
202 let xtol = options.xtol.unwrap_or(1e-8);
203 let gtol = options.gtol.unwrap_or(1e-8);
204 let max_nfev = options.max_nfev.unwrap_or(100 * x0.len());
205 let eps = options.diff_step.unwrap_or(1e-8);
206
207 let m = x0.len();
209 let mut x = x0.to_owned();
210 let mut res = residuals(x.as_slice().unwrap(), data.as_slice().unwrap());
211 let n = res.len();
212
213 let mut f = res.iter().map(|&r| r.powi(2)).sum::<f64>() / 2.0;
215
216 let mut nfev = 1;
218 let mut njev = 0;
219 let mut iter = 0;
220
221 let compute_jac = |x_params: &[f64], curr_res_vals: &Array1<f64>| -> (Array2<f64>, usize) {
223 let mut jac = Array2::zeros((n, m));
224 let mut count = 0;
225
226 for j in 0..m {
228 let mut x_h = Vec::from(x_params);
229 x_h[j] += eps;
230 let res_h = residuals(&x_h, data.as_slice().unwrap());
231 count += 1;
232
233 for i in 0..n {
234 jac[[i, j]] = (res_h[i] - curr_res_vals[i]) / eps;
235 }
236 }
237
238 (jac, count)
239 };
240
241 let (mut jac, jac_evals) = match &jacobian {
243 Some(jac_fn) => {
244 let j = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
245 njev += 1;
246 (j, 0)
247 }
248 None => {
249 let (j, count) = compute_jac(x.as_slice().unwrap(), &res);
250 nfev += count;
251 (j, count)
252 }
253 };
254
255 let mut g = jac.t().dot(&res);
257
258 let mut lambda = 1e-3;
260 let lambda_factor = 10.0;
261
262 while iter < max_nfev {
264 if g.iter().all(|&gi| gi.abs() < gtol) {
266 break;
267 }
268
269 let mut jt_j = jac.t().dot(&jac);
271
272 for i in 0..m {
274 jt_j[[i, i]] += lambda * jt_j[[i, i]].max(1e-10);
275 }
276
277 let neg_g = -&g;
280
281 let step = match solve(&jt_j, &neg_g) {
283 Some(s) => s,
284 None => {
285 lambda *= lambda_factor;
287 continue;
288 }
289 };
290
291 let mut x_new = Array1::zeros(m);
293 for i in 0..m {
294 x_new[i] = x[i] + step[i];
295 }
296
297 let res_new = residuals(x_new.as_slice().unwrap(), data.as_slice().unwrap());
299 nfev += 1;
300 let f_new = res_new.iter().map(|&r| r.powi(2)).sum::<f64>() / 2.0;
301
302 let actual_reduction = f - f_new;
304
305 let p1 = res.dot(&res);
307 let res_pred = res.clone() + jac.dot(&step);
308 let p2 = res_pred.dot(&res_pred);
309 let _predicted_reduction = 0.5 * (p1 - p2);
310
311 if actual_reduction > 0.0 {
313 lambda /= lambda_factor;
315
316 x = x_new;
318 res = res_new;
319 f = f_new;
320
321 if actual_reduction < ftol * f.abs() {
323 break;
324 }
325
326 if step
328 .iter()
329 .all(|&s| s.abs() < xtol * (1.0 + x.iter().map(|&xi| xi.abs()).sum::<f64>()))
330 {
331 break;
332 }
333
334 let (new_jac, jac_evals) = match &jacobian {
336 Some(jac_fn) => {
337 let j = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
338 njev += 1;
339 (j, 0)
340 }
341 None => {
342 let (j, count) = compute_jac(x.as_slice().unwrap(), &res);
343 nfev += count;
344 (j, count)
345 }
346 };
347
348 jac = new_jac;
349
350 g = jac.t().dot(&res);
352 } else {
353 lambda *= lambda_factor;
355 }
356
357 iter += 1;
358 }
359
360 let mut result = OptimizeResults::default();
362 result.x = x.clone();
363 result.fun = f;
364 result.jac = if let Some(jac_fn) = &jacobian {
365 let jac_array = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
366 njev += 1;
367 let (vec, _) = jac_array.into_raw_vec_and_offset();
368 Some(vec)
369 } else {
370 let (vec, _) = jac.into_raw_vec_and_offset();
371 Some(vec)
372 };
373 result.nfev = nfev;
374 result.njev = njev;
375 result.nit = iter;
376 result.success = iter < max_nfev;
377
378 if result.success {
379 result.message = "Optimization terminated successfully.".to_string();
380 } else {
381 result.message = "Maximum number of evaluations reached.".to_string();
382 }
383
384 Ok(result)
385}
386
387#[allow(dead_code)]
390fn solve(a: &Array2<f64>, b: &Array1<f64>) -> Option<Array1<f64>> {
391 use scirs2_linalg::solve;
392
393 solve(&a.view(), &b.view(), None).ok()
394}
395
396#[allow(dead_code)]
398fn least_squares_trf<F, J, D, S1, S2>(
399 residuals: F,
400 x0: &ArrayBase<S1, Ix1>,
401 jacobian: Option<J>,
402 data: &ArrayBase<S2, Ix1>,
403 options: &Options,
404) -> OptimizeResult<OptimizeResults<f64>>
405where
406 F: Fn(&[f64], &[D]) -> Array1<f64>,
407 J: Fn(&[f64], &[D]) -> Array2<f64>,
408 D: Clone,
409 S1: Data<Elem = f64>,
410 S2: Data<Elem = D>,
411{
412 let ftol = options.ftol.unwrap_or(1e-8);
414 let xtol = options.xtol.unwrap_or(1e-8);
415 let gtol = options.gtol.unwrap_or(1e-8);
416 let max_nfev = options.max_nfev.unwrap_or(100 * x0.len());
417 let eps = options.diff_step.unwrap_or(1e-8);
418
419 let m = x0.len();
421 let mut x = x0.to_owned();
422 let mut res = residuals(x.as_slice().unwrap(), data.as_slice().unwrap());
423 let n = res.len();
424
425 let mut f = res.iter().map(|&r| r.powi(2)).sum::<f64>() / 2.0;
427
428 let mut nfev = 1;
430 let mut njev = 0;
431 let mut iter = 0;
432
433 let compute_jac = |x_params: &[f64], curr_res_vals: &Array1<f64>| -> (Array2<f64>, usize) {
435 let mut jac = Array2::zeros((n, m));
436 let mut count = 0;
437
438 for j in 0..m {
440 let mut x_h = Vec::from(x_params);
441 x_h[j] += eps;
442 let res_h = residuals(&x_h, data.as_slice().unwrap());
443 count += 1;
444
445 for i in 0..n {
446 jac[[i, j]] = (res_h[i] - curr_res_vals[i]) / eps;
447 }
448 }
449
450 (jac, count)
451 };
452
453 let (mut jac, jac_evals) = match &jacobian {
455 Some(jac_fn) => {
456 let j = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
457 njev += 1;
458 (j, 0)
459 }
460 None => {
461 let (j, count) = compute_jac(x.as_slice().unwrap(), &res);
462 nfev += count;
463 (j, count)
464 }
465 };
466
467 let mut g = jac.t().dot(&res);
469
470 let mut delta = 100.0 * (1.0 + x.iter().map(|&xi| xi.abs()).sum::<f64>());
472
473 while iter < max_nfev {
475 if g.iter().all(|&gi| gi.abs() < gtol) {
477 break;
478 }
479
480 let jt_j = jac.t().dot(&jac);
482
483 let (step, predicted_reduction) = compute_trust_region_step(&jt_j, &g, delta);
485
486 if step
488 .iter()
489 .all(|&s| s.abs() < xtol * (1.0 + x.iter().map(|&xi| xi.abs()).sum::<f64>()))
490 {
491 break;
492 }
493
494 let x_new = &x + &step;
496
497 let res_new = residuals(x_new.as_slice().unwrap(), data.as_slice().unwrap());
499 nfev += 1;
500 let f_new = res_new.iter().map(|&r| r.powi(2)).sum::<f64>() / 2.0;
501
502 let actual_reduction = f - f_new;
504
505 let rho = if predicted_reduction > 0.0 {
507 actual_reduction / predicted_reduction
508 } else {
509 0.0
510 };
511
512 if rho < 0.25 {
514 delta *= 0.5;
515 } else if rho > 0.75 && step.iter().map(|&s| s * s).sum::<f64>().sqrt() >= 0.9 * delta {
516 delta *= 2.0;
517 }
518
519 if rho > 0.1 {
521 x = x_new;
523 res = res_new;
524 f = f_new;
525
526 if actual_reduction < ftol * f.abs() {
528 break;
529 }
530
531 let (new_jac, jac_evals) = match &jacobian {
533 Some(jac_fn) => {
534 let j = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
535 njev += 1;
536 (j, 0)
537 }
538 None => {
539 let (j, count) = compute_jac(x.as_slice().unwrap(), &res);
540 nfev += count;
541 (j, count)
542 }
543 };
544
545 jac = new_jac;
546
547 g = jac.t().dot(&res);
549 }
550
551 iter += 1;
552 }
553
554 let mut result = OptimizeResults::default();
556 result.x = x.clone();
557 result.fun = f;
558 result.jac = if let Some(jac_fn) = &jacobian {
559 let jac_array = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
560 njev += 1;
561 let (vec, _) = jac_array.into_raw_vec_and_offset();
562 Some(vec)
563 } else {
564 let (vec, _) = jac.into_raw_vec_and_offset();
565 Some(vec)
566 };
567 result.nfev = nfev;
568 result.njev = njev;
569 result.nit = iter;
570 result.success = iter < max_nfev;
571
572 if result.success {
573 result.message = "Optimization terminated successfully.".to_string();
574 } else {
575 result.message = "Maximum number of evaluations reached.".to_string();
576 }
577
578 Ok(result)
579}
580
581#[allow(dead_code)]
583fn compute_trust_region_step(
584 jt_j: &Array2<f64>,
585 g: &Array1<f64>,
586 delta: f64,
587) -> (Array1<f64>, f64) {
588 let n = g.len();
589
590 let sd_dir = -g;
592 let sd_norm_sq = g.iter().map(|&gi| gi * gi).sum::<f64>();
593
594 if sd_norm_sq < 1e-10 {
596 return (Array1::zeros(n), 0.0);
597 }
598
599 let sd_norm = sd_norm_sq.sqrt();
600
601 let sd_step = &sd_dir * (delta / sd_norm);
603
604 let gn_step = match solve(jt_j, &sd_dir) {
606 Some(step) => step,
607 None => {
608 let pred_red = 0.5 * g.dot(&sd_step);
610 return (sd_step, pred_red);
611 }
612 };
613
614 let gn_norm_sq = gn_step.iter().map(|&s| s * s).sum::<f64>();
616
617 if gn_norm_sq <= delta * delta {
619 let predicted_reduction = 0.5 * g.dot(&gn_step);
620 return (gn_step, predicted_reduction);
621 }
622
623 let gn_norm = gn_norm_sq.sqrt();
624
625 let sd_gn_dot = sd_dir.dot(&gn_step);
628 let sd_sq = sd_norm_sq; let gn_sq = gn_norm_sq; let a = sd_sq;
633 let b = 3.0 * sd_gn_dot;
634 let c = gn_sq - delta * delta;
635
636 if a < 1e-10 {
638 let step = &gn_step * (delta / gn_norm);
640 let predicted_reduction = 0.5 * g.dot(&step);
641 return (step, predicted_reduction);
642 }
643
644 let tau = if b * b - 4.0 * a * c > 0.0 {
646 (-b + (b * b - 4.0 * a * c).sqrt()) / (2.0 * a)
647 } else {
648 delta / sd_norm
649 };
650
651 let tau = tau.clamp(0.0, 1.0);
653
654 let step = if tau < 1.0 {
656 &sd_step * tau + &gn_step * (1.0 - tau)
658 } else {
659 &gn_step * (delta / gn_norm)
661 };
662
663 let predicted_reduction = 0.5 * g.dot(&step);
665
666 (step, predicted_reduction)
667}
668
669#[allow(dead_code)]
671fn least_squares_dogbox<F, J, D, S1, S2>(
672 residuals: F,
673 x0: &ArrayBase<S1, Ix1>,
674 jacobian: Option<J>,
675 data: &ArrayBase<S2, Ix1>,
676 options: &Options,
677) -> OptimizeResult<OptimizeResults<f64>>
678where
679 F: Fn(&[f64], &[D]) -> Array1<f64>,
680 J: Fn(&[f64], &[D]) -> Array2<f64>,
681 D: Clone,
682 S1: Data<Elem = f64>,
683 S2: Data<Elem = D>,
684{
685 let ftol = options.ftol.unwrap_or(1e-8);
687 let xtol = options.xtol.unwrap_or(1e-8);
688 let gtol = options.gtol.unwrap_or(1e-8);
689 let max_nfev = options.max_nfev.unwrap_or(100 * x0.len());
690 let eps = options.diff_step.unwrap_or(1e-8);
691
692 let m = x0.len();
694 let mut x = x0.to_owned();
695 let mut res = residuals(x.as_slice().unwrap(), data.as_slice().unwrap());
696 let n = res.len();
697
698 let mut f = res.iter().map(|&r| r.powi(2)).sum::<f64>() / 2.0;
700
701 let mut nfev = 1;
703 let mut njev = 0;
704 let mut iter = 0;
705
706 let lb = Array1::from_elem(m, f64::NEG_INFINITY);
708 let ub = Array1::from_elem(m, f64::INFINITY);
709
710 let mut delta = 1.0;
712 let max_delta = 1e3;
713 let min_delta = 1e-12;
714
715 let project_bounds = |x_vals: &mut Array1<f64>| {
717 for i in 0..m {
718 x_vals[i] = x_vals[i].max(lb[i]).min(ub[i]);
719 }
720 };
721
722 let compute_active_set = |x_vals: &Array1<f64>, g_vals: &Array1<f64>| -> Vec<bool> {
724 let mut active = vec![false; m];
725 let boundary_tol = 1e-10;
726
727 for i in 0..m {
728 let at_lower = (x_vals[i] - lb[i]).abs() < boundary_tol && g_vals[i] > 0.0;
729 let at_upper = (ub[i] - x_vals[i]).abs() < boundary_tol && g_vals[i] < 0.0;
730 active[i] = at_lower || at_upper;
731 }
732 active
733 };
734
735 let compute_jac = |x_params: &[f64], curr_res_vals: &Array1<f64>| -> (Array2<f64>, usize) {
737 let mut jac = Array2::zeros((n, m));
738 let mut count = 0;
739
740 for j in 0..m {
742 let mut x_h = Vec::from(x_params);
743 x_h[j] += eps;
744 let res_h = residuals(&x_h, data.as_slice().unwrap());
745 count += 1;
746
747 for i in 0..n {
748 jac[[i, j]] = (res_h[i] - curr_res_vals[i]) / eps;
749 }
750 }
751
752 (jac, count)
753 };
754
755 let mut jac = match &jacobian {
757 Some(jac_fn) => {
758 let j = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
759 njev += 1;
760 j
761 }
762 None => {
763 let (j, count) = compute_jac(x.as_slice().unwrap(), &res);
764 nfev += count;
765 j
766 }
767 };
768
769 let mut g = jac.t().dot(&res);
771
772 if g.iter().map(|&gi| gi.abs()).fold(0.0, f64::max) < gtol {
774 let mut result = OptimizeResults::default();
775 result.x = x.clone();
776 result.fun = f;
777 result.nfev = nfev;
778 result.njev = njev;
779 result.nit = iter;
780 result.success = true;
781 result.message = "Initial point satisfies convergence criteria.".to_string();
782 return Ok(result);
783 }
784
785 while iter < max_nfev {
787 let jt_j = jac.t().dot(&jac);
789
790 let active = compute_active_set(&x, &g);
792
793 let step = compute_dogbox_step(&jt_j, &g, &active, &lb, &ub, &x, delta);
795
796 let mut x_new = &x + &step;
798 project_bounds(&mut x_new);
799
800 let res_new = residuals(x_new.as_slice().unwrap(), data.as_slice().unwrap());
802 nfev += 1;
803
804 let f_new = res_new.iter().map(|&r| r.powi(2)).sum::<f64>() / 2.0;
806
807 let predicted_reduction = -g.dot(&step) - 0.5 * step.dot(&jt_j.dot(&step));
809
810 let actual_reduction = f - f_new;
812
813 let rho = if predicted_reduction > 0.0 {
815 actual_reduction / predicted_reduction
816 } else {
817 0.0
818 };
819
820 if rho < 0.25 {
822 delta *= 0.5;
823 } else if rho > 0.75 && step.iter().map(|&s| s * s).sum::<f64>().sqrt() >= 0.9 * delta {
824 delta = (2.0 * delta).min(max_delta);
825 }
826
827 if rho > 0.1 {
829 x = x_new;
831 res = res_new;
832 f = f_new;
833
834 if actual_reduction < ftol * f.abs() {
836 break;
837 }
838
839 if step.iter().map(|&s| s * s).sum::<f64>().sqrt() < xtol {
841 break;
842 }
843
844 let new_jac = match &jacobian {
846 Some(jac_fn) => {
847 let j = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
848 njev += 1;
849 j
850 }
851 None => {
852 let (j, count) = compute_jac(x.as_slice().unwrap(), &res);
853 nfev += count;
854 j
855 }
856 };
857
858 jac = new_jac;
859
860 g = jac.t().dot(&res);
862
863 if g.iter().map(|&gi| gi.abs()).fold(0.0, f64::max) < gtol {
865 break;
866 }
867 }
868
869 if delta < min_delta {
871 break;
872 }
873
874 iter += 1;
875 }
876
877 let mut result = OptimizeResults::default();
879 result.x = x.clone();
880 result.fun = f;
881 result.jac = if let Some(jac_fn) = &jacobian {
882 let jac_array = jac_fn(x.as_slice().unwrap(), data.as_slice().unwrap());
883 njev += 1;
884 let (vec, _) = jac_array.into_raw_vec_and_offset();
885 Some(vec)
886 } else {
887 let (vec, _) = jac.into_raw_vec_and_offset();
888 Some(vec)
889 };
890 result.nfev = nfev;
891 result.njev = njev;
892 result.nit = iter;
893 result.success = iter < max_nfev && delta >= min_delta;
894
895 if result.success {
896 result.message = "Optimization terminated successfully.".to_string();
897 } else if iter >= max_nfev {
898 result.message = "Maximum number of evaluations reached.".to_string();
899 } else {
900 result.message = "Trust region became too small.".to_string();
901 }
902
903 Ok(result)
904}
905
906#[allow(clippy::too_many_arguments)]
908#[allow(dead_code)]
909fn compute_dogbox_step(
910 jt_j: &Array2<f64>,
911 g: &Array1<f64>,
912 active: &[bool],
913 lb: &Array1<f64>,
914 ub: &Array1<f64>,
915 x: &Array1<f64>,
916 delta: f64,
917) -> Array1<f64> {
918 let n = g.len();
919
920 let free_vars: Vec<usize> = (0..n).filter(|&i| !active[i]).collect();
922
923 if free_vars.is_empty() {
924 return Array1::zeros(n);
926 }
927
928 let g_free = Array1::from_vec(free_vars.iter().map(|&i| g[i]).collect());
930 let mut jt_j_free = Array2::zeros((free_vars.len(), free_vars.len()));
931
932 for (i, &fi) in free_vars.iter().enumerate() {
933 for (j, &fj) in free_vars.iter().enumerate() {
934 jt_j_free[[i, j]] = jt_j[[fi, fj]];
935 }
936 }
937
938 let sd_dir_free = -&g_free;
940 let sd_norm_sq = g_free.iter().map(|&gi| gi * gi).sum::<f64>();
941
942 if sd_norm_sq < 1e-15 {
943 return Array1::zeros(n);
944 }
945
946 let sd_norm = sd_norm_sq.sqrt();
947
948 let gn_step_free = match solve(&jt_j_free, &sd_dir_free) {
950 Some(step) => step,
951 None => {
952 let step_free = &sd_dir_free * (delta / sd_norm);
954 let mut step = Array1::zeros(n);
955 for (i, &fi) in free_vars.iter().enumerate() {
956 step[fi] = step_free[i];
957 }
958 return bound_step(&step, x, lb, ub, delta);
959 }
960 };
961
962 let gn_norm_sq = gn_step_free.iter().map(|&s| s * s).sum::<f64>();
964
965 if gn_norm_sq <= delta * delta {
966 let mut step = Array1::zeros(n);
967 for (i, &fi) in free_vars.iter().enumerate() {
968 step[fi] = gn_step_free[i];
969 }
970 return bound_step(&step, x, lb, ub, delta);
971 }
972
973 let gn_norm = gn_norm_sq.sqrt();
975 let sd_step_free = &sd_dir_free * (delta / sd_norm);
976
977 let sd_gn_dot = sd_dir_free.dot(&gn_step_free);
979 let a = sd_norm_sq;
980 let b = 3.0 * sd_gn_dot;
981 let c = gn_norm_sq - delta * delta;
982
983 let tau = if a > 1e-15 && b * b - 4.0 * a * c > 0.0 {
984 (-b + (b * b - 4.0 * a * c).sqrt()) / (2.0 * a)
985 } else {
986 delta / sd_norm
987 };
988
989 let tau = tau.clamp(0.0, 1.0);
990
991 let step_free = if tau < 1.0 {
992 &sd_step_free * tau + &gn_step_free * (1.0 - tau)
993 } else {
994 &gn_step_free * (delta / gn_norm)
995 };
996
997 let mut step = Array1::zeros(n);
999 for (i, &fi) in free_vars.iter().enumerate() {
1000 step[fi] = step_free[i];
1001 }
1002
1003 bound_step(&step, x, lb, ub, delta)
1004}
1005
1006#[allow(dead_code)]
1008fn bound_step(
1009 step: &Array1<f64>,
1010 x: &Array1<f64>,
1011 lb: &Array1<f64>,
1012 ub: &Array1<f64>,
1013 delta: f64,
1014) -> Array1<f64> {
1015 let mut bounded_step = step.clone();
1016
1017 for i in 0..step.len() {
1019 let x_new = x[i] + bounded_step[i];
1020 if x_new < lb[i] {
1021 bounded_step[i] = lb[i] - x[i];
1022 } else if x_new > ub[i] {
1023 bounded_step[i] = ub[i] - x[i];
1024 }
1025 }
1026
1027 let step_norm = bounded_step.iter().map(|&s| s * s).sum::<f64>().sqrt();
1029 if step_norm > delta {
1030 bounded_step *= delta / step_norm;
1031 }
1032
1033 bounded_step
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038 use super::*;
1039 use scirs2_core::ndarray::array;
1040
1041 fn residual(x: &[f64], _: &[f64]) -> Array1<f64> {
1042 let y = array![x[0] + 2.0 * x[1] - 2.0, x[0] + x[1] - 1.0];
1043 y
1044 }
1045
1046 fn jacobian(x: &[f64], _: &[f64]) -> Array2<f64> {
1047 array![[1.0, 2.0], [1.0, 1.0]]
1048 }
1049
1050 #[test]
1051 fn test_least_squares_placeholder() {
1052 let x0 = array![0.0, 0.0];
1053 let data = array![];
1054
1055 let options = Options {
1057 max_nfev: Some(1), ..Options::default()
1059 };
1060
1061 let result = least_squares(
1062 residual,
1063 &x0.view(),
1064 Method::LevenbergMarquardt,
1065 Some(jacobian),
1066 &data.view(),
1067 Some(options),
1068 )
1069 .unwrap();
1070
1071 assert!(!result.success);
1073
1074 assert!(result.fun > 0.0);
1077
1078 assert!(result.jac.is_some());
1080 }
1081
1082 #[test]
1083 fn test_levenberg_marquardt() {
1084 let x0 = array![-1.0, -1.0];
1092 let data = array![];
1093
1094 let options = Options {
1095 max_nfev: Some(100),
1096 xtol: Some(1e-6),
1097 ftol: Some(1e-6),
1098 ..Options::default()
1099 };
1100
1101 let result = least_squares(
1102 residual,
1103 &x0.view(),
1104 Method::LevenbergMarquardt,
1105 Some(jacobian),
1106 &data.view(),
1107 Some(options),
1108 )
1109 .unwrap();
1110
1111 assert!(result.success);
1113
1114 assert!((result.x[0] - 0.0).abs() < 1e-4);
1116 assert!((result.x[1] - 1.0).abs() < 1e-4);
1117
1118 assert!(result.fun < 1e-8);
1120
1121 println!(
1123 "LM result: x = {:?}, f = {}, iterations = {}",
1124 result.x, result.fun, result.nit
1125 );
1126 }
1127
1128 #[test]
1129 fn test_trust_region_reflective() {
1130 let x0 = array![0.0, 0.0]; let data = array![];
1139
1140 let options = Options {
1141 max_nfev: Some(1000), xtol: Some(1e-5), ftol: Some(1e-5), ..Options::default()
1145 };
1146
1147 let result = least_squares(
1148 residual,
1149 &x0.view(),
1150 Method::TrustRegionReflective,
1151 Some(jacobian),
1152 &data.view(),
1153 Some(options),
1154 )
1155 .unwrap();
1156
1157 let initial_resid = residual(&[0.0, 0.0], &[]);
1162 let initial_value = 0.5 * initial_resid.iter().map(|&r| r * r).sum::<f64>();
1163
1164 println!(
1166 "TRF result: x = {:?}, f = {}, initial = {}, iterations = {}",
1167 result.x, result.fun, initial_value, result.nit
1168 );
1169
1170 assert!(result.fun <= initial_value || result.success);
1172 }
1173
1174 #[test]
1175 fn test_rosenbrock_least_squares() {
1176 fn rosenbrock_residual(x: &[f64], _: &[f64]) -> Array1<f64> {
1178 array![10.0 * (x[1] - x[0].powi(2)), 1.0 - x[0]]
1179 }
1180
1181 fn rosenbrock_jacobian(x: &[f64], _: &[f64]) -> Array2<f64> {
1182 array![[-20.0 * x[0], 10.0], [-1.0, 0.0]]
1183 }
1184
1185 let x0_lm = array![-1.2, 1.0]; let x0_trf = array![0.5, 0.5]; let data = array![];
1189
1190 let options_common = Options {
1191 xtol: Some(1e-6),
1192 ftol: Some(1e-6),
1193 ..Options::default()
1194 };
1195
1196 let options_trf = Options {
1197 max_nfev: Some(300), ..options_common.clone()
1199 };
1200
1201 let options_lm = Options {
1202 max_nfev: Some(1000), ..options_common
1204 };
1205
1206 let result_trf = least_squares(
1208 rosenbrock_residual,
1209 &x0_trf.view(), Method::TrustRegionReflective,
1211 Some(rosenbrock_jacobian),
1212 &data.view(),
1213 Some(options_trf),
1214 )
1215 .unwrap();
1216
1217 let result_lm = least_squares(
1219 rosenbrock_residual,
1220 &x0_lm.view(), Method::LevenbergMarquardt,
1222 Some(rosenbrock_jacobian),
1223 &data.view(),
1224 Some(options_lm),
1225 )
1226 .unwrap();
1227
1228 println!(
1230 "TRF Rosenbrock: x = {:?}, f = {}, iterations = {}",
1231 result_trf.x, result_trf.fun, result_trf.nit
1232 );
1233 println!(
1234 "LM Rosenbrock: x = {:?}, f = {}, iterations = {}",
1235 result_lm.x, result_lm.fun, result_lm.nit
1236 );
1237
1238 let initial_resid_trf = rosenbrock_residual(&[0.5, 0.5], &[]);
1240 let initial_value_trf = 0.5 * initial_resid_trf.iter().map(|&r| r * r).sum::<f64>();
1241 println!("TRF initial value: {}", initial_value_trf);
1242
1243 assert!(result_trf.fun < 100.0); assert!(result_lm.success);
1248 assert!((result_lm.x[0] - 1.0).abs() < 1e-2);
1249 assert!((result_lm.x[1] - 1.0).abs() < 1e-2);
1250 }
1251}