1use molrs::types::F;
6pub mod cg;
7pub mod spg;
8
9use crate::constraints::EvalMode;
10use crate::numerics::{numeric_controls, positive_norm_floor};
11use crate::objective::Objective;
12
13pub struct GencanParams {
15 pub epsgpsn: F,
16 pub maxit: usize,
17 pub maxfc: usize,
18 pub delmin: F,
19 pub iprint: i32,
20 pub ncomp: usize,
21}
22
23impl Default for GencanParams {
24 fn default() -> Self {
25 Self {
26 epsgpsn: 1.0e-6,
27 maxit: 20,
28 maxfc: 200, delmin: 1.0e-2, iprint: 0,
31 ncomp: 50,
32 }
33 }
34}
35
36pub struct GencanResult {
38 pub f: F,
39 pub gpsupn: F,
40 pub iter: usize,
41 pub fcnt: usize,
42 pub gcnt: usize,
43 pub cgcnt: usize,
44 pub inform: i32,
47}
48
49pub struct GencanWorkspace {
51 g: Vec<F>,
52 ind: Vec<usize>,
53 d: Vec<F>,
54 s: Vec<F>,
55 y: Vec<F>,
56 cg_scratch: cg::CgScratch,
57 spg_scratch: spg::SpgScratch,
58 tnls_scratch: TnLsScratch,
59}
60
61impl GencanWorkspace {
62 pub fn new() -> Self {
63 Self {
64 g: Vec::new(),
65 ind: Vec::new(),
66 d: Vec::new(),
67 s: Vec::new(),
68 y: Vec::new(),
69 cg_scratch: cg::CgScratch::new(0),
70 spg_scratch: spg::SpgScratch::new(0),
71 tnls_scratch: TnLsScratch::new(0),
72 }
73 }
74
75 fn ensure_len(&mut self, n: usize) {
76 if self.g.len() != n {
77 self.g.resize(n, 0.0);
78 }
79 if self.d.len() != n {
80 self.d.resize(n, 0.0);
81 }
82 if self.s.len() != n {
83 self.s.resize(n, 0.0);
84 }
85 if self.y.len() != n {
86 self.y.resize(n, 0.0);
87 }
88 if self.ind.capacity() < n {
89 self.ind.reserve(n - self.ind.capacity());
90 }
91 }
92}
93
94impl Default for GencanWorkspace {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100pub fn pgencan(
109 x: &mut [F],
110 obj: &mut dyn Objective,
111 params: &GencanParams,
112 precision: F,
113 workspace: &mut GencanWorkspace,
114) -> GencanResult {
115 let n = x.len();
116
117 let mut l = vec![0.0 as F; n];
118 let mut u = vec![0.0 as F; n];
119 obj.bounds(&mut l, &mut u);
120
121 gencan(x, &l, &u, obj, params, precision, workspace)
122}
123
124#[allow(unused_assignments)]
127pub fn gencan(
128 x: &mut [F],
129 l: &[F],
130 u: &[F],
131 obj: &mut dyn Objective,
132 params: &GencanParams,
133 precision: F,
134 workspace: &mut GencanWorkspace,
135) -> GencanResult {
136 let n = x.len();
137 workspace.ensure_len(n);
138
139 const INFREL: F = 1.0e20;
141 const INFABS: F = F::MAX;
142 const BETA_LS: F = 0.5;
143 const GAMMA: F = 1.0e-4;
144 const THETA: F = 1.0e-6;
145 const SIGMA1: F = 0.1;
146 const SIGMA2: F = 0.9;
147 const MAXEXTRAP: usize = 100;
148 const MININTERP: usize = 4;
149 const NINT: F = 2.0;
150 const NEXT: F = 2.0;
151 const ETA: F = 0.9;
152 const LSPGMA: F = 1.0e10;
153 const LSPGMI: F = 1.0e-10;
154 const FMIN: F = 1.0e-5;
155 const EPSGPEN: F = 0.0;
156 let numeric = numeric_controls();
157
158 let cgepsi = 0.1 as F;
159 let cgepsf = 1.0e-5 as F;
160 let cggpnf = F::max(1.0e-4, params.epsgpsn);
161 let epsnqmp = 1.0e-4 as F;
162 let maxitnqmp = 5usize;
163 let epsnfp = 0.0 as F;
164 let maxitnfp = params.maxit;
165 let maxitngp = 1000usize;
166
167 for i in 0..n {
169 x[i] = x[i].clamp(l[i], u[i]);
170 }
171
172 let mut fcnt = 0usize;
174 let g = &mut workspace.g;
175 g.fill(0.0);
176 let mut f = obj.evaluate(x, EvalMode::FAndGradient, Some(g)).f_total;
177
178 if converged(obj, precision) {
180 return GencanResult {
181 f,
182 gpsupn: 0.0,
183 iter: 0,
184 fcnt,
185 gcnt: 0,
186 cgcnt: 0,
187 inform: 0,
188 };
189 }
190 fcnt += 1;
191 let mut gcnt = 1usize;
192 let mut cgcnt = 0usize;
193
194 let mut xnorm = x.iter().map(|xi| xi * xi).sum::<F>().sqrt();
196
197 let ind = &mut workspace.ind;
198 let cg_scratch = &mut workspace.cg_scratch;
199 let spg_scratch = &mut workspace.spg_scratch;
200 let tnls_scratch = &mut workspace.tnls_scratch;
201
202 let (mut gpsupn, mut gpeucn2, mut gieucn2, mut nind) =
204 projected_gradient_info(n, x, g.as_slice(), l, u, ind);
205
206 let (acgeps, bcgeps) = gp_ieee_signal(gpsupn, cgepsf, cgepsi, cggpnf);
208
209 let gpsupn0 = gpsupn;
212
213 let mut iter = 0usize;
214 let mut inform = 7i32; let mut delta = 0.0 as F; let mut sts = 0.0 as F;
222 let mut sty = 0.0 as F;
223 let ometa2 = (1.0 - ETA).powi(2);
224
225 let mut fprev = INFABS;
227 let mut bestprog = 0.0 as F;
228 let mut itnfp = 0usize;
229 let mut lastgpns = vec![INFABS; maxitngp];
230
231 let d = &mut workspace.d;
233 let s = &mut workspace.s;
234 let y = &mut workspace.y;
235
236 loop {
238 if converged(obj, precision) {
240 break;
241 }
242
243 if gpeucn2 <= EPSGPEN * EPSGPEN {
244 inform = 0;
245 break;
246 }
247 if gpsupn <= params.epsgpsn {
249 inform = 1;
250 break;
251 }
252
253 let currprog = fprev - f;
255 bestprog = bestprog.max(currprog);
256 if currprog <= epsnfp * bestprog {
257 itnfp += 1;
258 if itnfp >= maxitnfp {
259 inform = 2;
260 break;
261 }
262 } else {
263 itnfp = 0;
264 }
265
266 let gpnmax = lastgpns.iter().copied().fold(0.0 as F, F::max);
268 lastgpns[iter % maxitngp] = gpeucn2;
269 if gpeucn2 >= gpnmax {
270 inform = 3;
271 break;
272 }
273
274 if f <= FMIN {
275 inform = 4;
276 break;
277 }
278 if iter >= params.maxit {
279 inform = 7;
280 break;
281 }
282 if fcnt >= params.maxfc {
283 inform = 8;
284 break;
285 }
286
287 iter += 1;
289 fprev = f;
290
291 s.copy_from_slice(x);
293 y.copy_from_slice(g.as_slice());
294
295 if gieucn2 <= ometa2 * gpeucn2 {
296 let lamspg = if iter == 1 || sty <= 0.0 {
298 F::max(1.0, xnorm) / gpeucn2.sqrt().max(positive_norm_floor())
299 } else {
300 sts / sty
301 };
302 let lamspg = lamspg.clamp(LSPGMI, LSPGMA);
303
304 let spg_res = spg::spgls(
305 n,
306 x,
307 g.as_slice(),
308 l,
309 u,
310 lamspg,
311 f,
312 NINT,
313 MININTERP,
314 FMIN,
315 params.maxfc,
316 fcnt,
317 GAMMA,
318 SIGMA1,
319 SIGMA2,
320 numeric.sterel,
321 numeric.steabs,
322 numeric.epsrel,
323 numeric.epsabs,
324 spg_scratch,
325 obj,
326 );
327 f = spg_res.f;
328 fcnt = spg_res.fcnt;
329 x.copy_from_slice(&spg_scratch.xtrial);
330
331 if spg_res.inform < 0 {
332 inform = spg_res.inform;
333 break;
334 }
335
336 obj.evaluate(x, EvalMode::GradientOnly, Some(g.as_mut_slice()));
337 gcnt += 1;
338 } else {
339 delta = if iter == 1 {
345 F::max(params.delmin, 0.1 * F::max(1.0, xnorm))
346 } else {
347 F::max(params.delmin, 10.0 * sts.sqrt())
348 };
349
350 let cgeps = compute_cgeps(gpsupn, acgeps, bcgeps, cgepsf, cgepsi);
351
352 let cgmaxit = {
356 let mut kappa = (gpsupn / gpsupn0).log10() / (params.epsgpsn / gpsupn0).log10();
357 kappa = F::max(0.0, F::min(1.0, kappa));
358
359 let nind_f = nind as F;
360 let base = (1.0 - kappa) * F::max(1.0, 10.0 * nind_f.log10()) + kappa * nind_f;
361 usize::min(20, base as usize)
362 };
363
364 let cg_res = cg::cg_solve(
365 nind,
366 ind,
367 n,
368 x,
369 g.as_slice(),
370 delta,
371 l,
372 u,
373 cgeps,
374 epsnqmp,
375 maxitnqmp,
376 cgmaxit,
377 false, 1, THETA,
380 numeric.sterel,
381 numeric.steabs,
382 numeric.epsrel,
383 numeric.epsabs,
384 INFREL,
385 INFABS,
386 d,
387 cg_scratch,
388 obj,
389 );
390 cgcnt += cg_res.iter;
391
392 let mut amax = INFABS;
394 let mut rbdtype = 0i32;
395 let mut rbdind = if nind > 0 { ind[0] } else { 0 };
396
397 if cg_res.inform == 2 {
398 amax = 1.0;
399 rbdtype = cg_res.rbdtype;
400 rbdind = cg_res.rbdind.unwrap_or(rbdind);
401 } else {
402 for &ii in &ind[..nind] {
403 if d[ii] > 0.0 {
404 let amaxx = (u[ii] - x[ii]) / d[ii];
405 if amaxx < amax {
406 amax = amaxx;
407 rbdind = ii;
408 rbdtype = 2;
409 }
410 } else if d[ii] < 0.0 {
411 let amaxx = (l[ii] - x[ii]) / d[ii];
412 if amaxx < amax {
413 amax = amaxx;
414 rbdind = ii;
415 rbdtype = 1;
416 }
417 }
418 }
419 }
420
421 let ls_res = tn_linesearch(
423 nind,
424 ind,
425 n,
426 x,
427 g.as_slice(),
428 d,
429 l,
430 u,
431 f,
432 amax,
433 rbdtype,
434 rbdind,
435 NINT,
436 NEXT,
437 MININTERP,
438 MAXEXTRAP,
439 FMIN,
440 params.maxfc,
441 fcnt,
442 gcnt,
443 GAMMA,
444 BETA_LS,
445 numeric.sterel,
446 numeric.steabs,
447 SIGMA1,
448 SIGMA2,
449 numeric.epsrel,
450 numeric.epsabs,
451 tnls_scratch,
452 obj,
453 );
454 f = ls_res.f;
455 fcnt = ls_res.fcnt;
456 gcnt = ls_res.gcnt;
457 x.copy_from_slice(&tnls_scratch.xret);
458 g.copy_from_slice(&tnls_scratch.gret);
459
460 if ls_res.inform < 0 {
461 inform = ls_res.inform;
462 break;
463 }
464 inform = ls_res.inform;
465
466 if ls_res.inform == 6 {
468 let lamspg = if iter == 1 || sty <= 0.0 {
469 F::max(1.0, xnorm) / gpeucn2.sqrt().max(positive_norm_floor())
470 } else {
471 sts / sty
472 };
473 let lamspg = lamspg.clamp(LSPGMI, LSPGMA);
474
475 let spg_res = spg::spgls(
476 n,
477 x,
478 g.as_slice(),
479 l,
480 u,
481 lamspg,
482 f,
483 NINT,
484 MININTERP,
485 FMIN,
486 params.maxfc,
487 fcnt,
488 GAMMA,
489 SIGMA1,
490 SIGMA2,
491 numeric.sterel,
492 numeric.steabs,
493 numeric.epsrel,
494 numeric.epsabs,
495 spg_scratch,
496 obj,
497 );
498 f = spg_res.f;
499 fcnt = spg_res.fcnt;
500 x.copy_from_slice(&spg_scratch.xtrial);
501
502 if spg_res.inform < 0 {
503 inform = spg_res.inform;
504 break;
505 }
506
507 let infotmp = spg_res.inform;
508 obj.evaluate(x, EvalMode::GradientOnly, Some(g.as_mut_slice()));
509 gcnt += 1;
510 inform = infotmp;
511 }
512 }
513
514 for i in 0..n {
516 if x[i] <= l[i] + (numeric.epsrel * l[i].abs()).max(numeric.epsabs) {
517 x[i] = l[i];
518 } else if x[i] >= u[i] - (numeric.epsrel * u[i].abs()).max(numeric.epsabs) {
519 x[i] = u[i];
520 }
521 }
522
523 xnorm = x.iter().map(|xi| xi * xi).sum::<F>().sqrt();
525
526 sts = 0.0;
528 sty = 0.0;
529 for i in 0..n {
530 let ds = x[i] - s[i];
531 let dy = g[i] - y[i];
532 sts += ds * ds;
533 sty += ds * dy;
534 }
535
536 let pg = projected_gradient_info(n, x, g.as_slice(), l, u, ind);
538 gpsupn = pg.0;
539 gpeucn2 = pg.1;
540 gieucn2 = pg.2;
541 nind = pg.3;
542 }
543
544 GencanResult {
548 f,
549 gpsupn,
550 iter,
551 fcnt,
552 gcnt,
553 cgcnt,
554 inform,
555 }
556}
557
558fn projected_gradient_info(
561 n: usize,
562 x: &[F],
563 g: &[F],
564 l: &[F],
565 u: &[F],
566 ind: &mut Vec<usize>,
567) -> (F, F, F, usize) {
568 let mut gpsupn = 0.0 as F;
569 let mut gpeucn2 = 0.0 as F;
570 let mut gieucn2 = 0.0 as F;
571
572 ind.clear();
573 for i in 0..n {
574 let gpi = (x[i] - g[i]).clamp(l[i], u[i]) - x[i];
575 gpsupn = gpsupn.max(gpi.abs());
576 gpeucn2 += gpi * gpi;
577 if x[i] > l[i] && x[i] < u[i] {
578 gieucn2 += gpi * gpi;
579 ind.push(i);
580 }
581 }
582
583 (gpsupn, gpeucn2, gieucn2, ind.len())
584}
585
586fn converged(obj: &dyn Objective, precision: F) -> bool {
589 obj.fdist() < precision && obj.frest() < precision
590}
591
592fn gp_ieee_signal(gpsupn: F, cgepsf: F, cgepsi: F, cggpnf: F) -> (F, F) {
594 if gpsupn > 0.0 {
595 let acgeps = (cgepsf / cgepsi).log10() / (cggpnf / gpsupn).log10();
596 let bcgeps = cgepsi.log10() - acgeps * gpsupn.log10();
597 (acgeps, bcgeps)
598 } else {
599 (0.0, cgepsf)
600 }
601}
602
603fn compute_cgeps(gpsupn: F, acgeps: F, bcgeps: F, cgepsf: F, cgepsi: F) -> F {
604 let cgeps = (10.0 as F).powf(acgeps * gpsupn.log10() + bcgeps);
605 cgeps.clamp(cgepsf, cgepsi)
606}
607
608struct LsResult {
610 pub f: F,
611 pub fcnt: usize,
612 pub gcnt: usize,
613 pub inform: i32,
614}
615
616struct TnLsScratch {
618 xret: Vec<F>,
619 gret: Vec<F>,
620 xplus: Vec<F>,
621 xtmp: Vec<F>,
622 gplus: Vec<F>,
623}
624
625impl TnLsScratch {
626 fn new(n: usize) -> Self {
627 Self {
628 xret: vec![0.0; n],
629 gret: vec![0.0; n],
630 xplus: vec![0.0; n],
631 xtmp: vec![0.0; n],
632 gplus: vec![0.0; n],
633 }
634 }
635
636 fn ensure_len(&mut self, n: usize) {
637 if self.xret.len() != n {
638 self.xret.resize(n, 0.0);
639 }
640 if self.gret.len() != n {
641 self.gret.resize(n, 0.0);
642 }
643 if self.xplus.len() != n {
644 self.xplus.resize(n, 0.0);
645 }
646 if self.xtmp.len() != n {
647 self.xtmp.resize(n, 0.0);
648 }
649 if self.gplus.len() != n {
650 self.gplus.resize(n, 0.0);
651 }
652 }
653}
654
655#[allow(clippy::too_many_arguments)]
656#[allow(unused_assignments)]
657fn tn_linesearch(
658 nind: usize,
659 ind: &[usize],
660 n: usize,
661 x: &[F],
662 g: &[F],
663 d: &[F],
664 l: &[F],
665 u: &[F],
666 f0: F,
667 amax: F,
668 rbdtype: i32,
669 rbdind: usize,
670 nint: F,
671 next: F,
672 mininterp: usize,
673 maxextrap: usize,
674 fmin: F,
675 maxfc: usize,
676 mut fcnt: usize,
677 mut gcnt: usize,
678 gamma: F,
679 beta: F,
680 _sterel: F,
681 _steabs: F,
682 sigma1: F,
683 sigma2: F,
684 epsrel: F,
685 epsabs: F,
686 scratch: &mut TnLsScratch,
687 obj: &mut dyn Objective,
688) -> LsResult {
689 scratch.ensure_len(n);
690 let nind = nind.min(ind.len());
691 let xret = &mut scratch.xret;
692 let gret = &mut scratch.gret;
693 let xplus = &mut scratch.xplus;
694 let xtmp = &mut scratch.xtmp;
695 let gplus = &mut scratch.gplus;
696 xret.copy_from_slice(x);
697 gret.copy_from_slice(g);
698 let mut fret = f0;
699
700 let mut gplus_valid = false;
701
702 let mut gtd = 0.0 as F;
704 for &ii in &ind[..nind] {
705 gtd += g[ii] * d[ii];
706 }
707
708 let mut alpha = (1.0 as F).min(amax);
710 xplus.copy_from_slice(x);
711 for &ii in &ind[..nind] {
712 xplus[ii] = x[ii] + alpha * d[ii];
713 }
714 if alpha == amax && rbdtype != 0 {
715 if rbdtype == 1 {
716 xplus[rbdind] = l[rbdind];
717 } else {
718 xplus[rbdind] = u[rbdind];
719 }
720 }
721
722 let mut fplus = obj.evaluate(xplus, EvalMode::FOnly, None).f_total;
723 fcnt += 1;
724
725 let mut do_extrap = false;
726
727 if amax > 1.0 {
729 if fplus <= f0 + gamma * alpha * gtd {
730 obj.evaluate(xplus, EvalMode::GradientOnly, Some(gplus));
731 gcnt += 1;
732 gplus_valid = true;
733
734 let mut gptd = 0.0 as F;
735 for &ii in &ind[..nind] {
736 gptd += gplus[ii] * d[ii];
737 }
738
739 if gptd < beta * gtd {
740 do_extrap = true;
741 } else {
742 xret.copy_from_slice(xplus);
743 gret.copy_from_slice(gplus);
744 fret = fplus;
745 return LsResult {
746 f: fret,
747 fcnt,
748 gcnt,
749 inform: 0,
750 };
751 }
752 }
753 } else if fplus < f0 {
754 do_extrap = true;
755 }
756
757 if do_extrap {
761 let mut extrap = 0usize;
762
763 loop {
764 if fplus <= fmin {
765 xret.copy_from_slice(xplus);
766 fret = fplus;
767 if extrap != 0 || amax <= 1.0 {
768 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
769 gcnt += 1;
770 } else if gplus_valid {
771 gret.copy_from_slice(gplus);
772 }
773 return LsResult {
774 f: fret,
775 fcnt,
776 gcnt,
777 inform: 4,
778 };
779 }
780
781 if fcnt >= maxfc {
782 xret.copy_from_slice(xplus);
783 fret = fplus;
784 if extrap != 0 || amax <= 1.0 {
785 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
786 gcnt += 1;
787 } else if gplus_valid {
788 gret.copy_from_slice(gplus);
789 }
790 return LsResult {
791 f: fret,
792 fcnt,
793 gcnt,
794 inform: 8,
795 };
796 }
797
798 if extrap >= maxextrap {
799 xret.copy_from_slice(xplus);
800 fret = fplus;
801 if extrap != 0 || amax <= 1.0 {
802 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
803 gcnt += 1;
804 } else if gplus_valid {
805 gret.copy_from_slice(gplus);
806 }
807 return LsResult {
808 f: fret,
809 fcnt,
810 gcnt,
811 inform: 7,
812 };
813 }
814
815 let atmp = if alpha < amax && next * alpha > amax {
816 amax
817 } else {
818 next * alpha
819 };
820
821 xtmp.copy_from_slice(x);
822 for &ii in &ind[..nind] {
823 xtmp[ii] = x[ii] + atmp * d[ii];
824 }
825 if atmp == amax && rbdtype != 0 {
826 if rbdtype == 1 {
827 xtmp[rbdind] = l[rbdind];
828 } else {
829 xtmp[rbdind] = u[rbdind];
830 }
831 }
832 if atmp > amax {
833 for &ii in &ind[..nind] {
834 xtmp[ii] = xtmp[ii].clamp(l[ii], u[ii]);
835 }
836 }
837
838 if alpha > amax {
839 let mut samep = true;
840 for &ii in &ind[..nind] {
841 if (xtmp[ii] - xplus[ii]).abs() > (epsrel * xplus[ii].abs()).max(epsabs) {
842 samep = false;
843 break;
844 }
845 }
846
847 if samep {
848 xret.copy_from_slice(xplus);
849 fret = fplus;
850 if extrap != 0 || amax <= 1.0 {
851 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
852 gcnt += 1;
853 } else if gplus_valid {
854 gret.copy_from_slice(gplus);
855 }
856 return LsResult {
857 f: fret,
858 fcnt,
859 gcnt,
860 inform: 0,
861 };
862 }
863 }
864
865 let ftmp = obj.evaluate(xtmp, EvalMode::FOnly, None).f_total;
866 fcnt += 1;
867
868 if ftmp < fplus {
869 alpha = atmp;
870 fplus = ftmp;
871 xplus.copy_from_slice(xtmp);
872 gplus_valid = false;
873 extrap += 1;
874 continue;
875 }
876
877 xret.copy_from_slice(xplus);
878 fret = fplus;
879 if extrap != 0 || amax <= 1.0 {
880 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
881 gcnt += 1;
882 } else if gplus_valid {
883 gret.copy_from_slice(gplus);
884 } else {
885 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
886 gcnt += 1;
887 }
888 return LsResult {
889 f: fret,
890 fcnt,
891 gcnt,
892 inform: 0,
893 };
894 }
895 }
896
897 let mut interp = 0usize;
901 loop {
902 if fplus <= fmin {
903 xret.copy_from_slice(xplus);
904 fret = fplus;
905 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
906 gcnt += 1;
907 return LsResult {
908 f: fret,
909 fcnt,
910 gcnt,
911 inform: 4,
912 };
913 }
914
915 if fcnt >= maxfc {
916 if fplus < f0 {
917 xret.copy_from_slice(xplus);
918 fret = fplus;
919 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
920 gcnt += 1;
921 }
922 return LsResult {
923 f: fret,
924 fcnt,
925 gcnt,
926 inform: 8,
927 };
928 }
929
930 if fplus <= f0 + gamma * alpha * gtd {
931 xret.copy_from_slice(xplus);
932 fret = fplus;
933 obj.evaluate(xret, EvalMode::GradientOnly, Some(gret));
934 gcnt += 1;
935 return LsResult {
936 f: fret,
937 fcnt,
938 gcnt,
939 inform: 0,
940 };
941 }
942
943 interp += 1;
944 if alpha < sigma1 {
945 alpha /= nint;
946 } else {
947 let denom = 2.0 * (fplus - f0 - alpha * gtd);
948 let atmp = if denom != 0.0 {
949 (-gtd * alpha * alpha) / denom
950 } else {
951 alpha / nint
952 };
953 if atmp < sigma1 || atmp > sigma2 * alpha {
954 alpha /= nint;
955 } else {
956 alpha = atmp;
957 }
958 }
959
960 xplus.copy_from_slice(x);
961 for &ii in &ind[..nind] {
962 xplus[ii] = x[ii] + alpha * d[ii];
963 }
964
965 fplus = obj.evaluate(xplus, EvalMode::FOnly, None).f_total;
966 fcnt += 1;
967
968 let mut samep = true;
969 for &ii in &ind[..nind] {
970 if (alpha * d[ii]).abs() > (epsrel * x[ii].abs()).max(epsabs) {
971 samep = false;
972 break;
973 }
974 }
975 if interp >= mininterp && samep {
976 return LsResult {
977 f: fret,
978 fcnt,
979 gcnt,
980 inform: 6,
981 };
982 }
983 }
984}