Skip to main content

edge_rs/numeric/
bobyqa.rs

1//! Powell's BOBYQA, bound-constrained derivative-free minimisation, as NLopt
2//! 2.7.1 runs it.
3//!
4//! nebula's stage two calls `nloptr::bobyqa`, which is NLopt's C translation of
5//! Powell's Fortran (`src/algs/bobyqa/bobyqa.c`) behind NLopt's own set-up: the
6//! default initial step of `nlopt_set_default_initial_step`, a rescaling that
7//! makes those steps equal, `rhobeg` from the step and `rhoend = xtol_rel *
8//! rhobeg`. This is a line-by-line port of all of it, so for the same objective
9//! values it asks for the same points in the same order. The objective is
10//! reverse-communicated: [`BobyqaStepper`] pauses wherever the C calls `calfun`
11//! (`prelim`, the trust-region step and `rescue`), so the GPU path can batch
12//! evaluations across genes.
13//!
14//! Arrays keep Powell's one-based indexing: one-dimensional state carries an
15//! unused element zero, matrices go through index helpers, and the subroutines
16//! that take zero-based slices subtract one. That keeps every line comparable
17//! with the C.
18//!
19//! Only the stopping tests nloptr's defaults reach are kept: `xtol_rel` through
20//! `rhoend` and `maxeval`. `ftol_rel = ftol_abs = 0` never fires in NLopt, and
21//! `stopval = -inf` never either.
22//!
23//! ### References
24//!
25//! Powell, The BOBYQA algorithm for bound constrained optimization without
26//! derivatives, DAMTP 2009/NA06, 2009
27
28#![allow(clippy::needless_range_loop, clippy::too_many_arguments)]
29
30use crate::errors::EdgeErrors;
31
32////////////
33// Params //
34////////////
35
36/// Stopping knobs, nloptr's defaults.
37#[derive(Clone, Copy, Debug)]
38pub struct BobyqaParams {
39    /// Relative tolerance on the variables, turned into `rhoend`.
40    pub xtol_rel: f64,
41    /// Evaluation budget.
42    pub maxeval: usize,
43}
44
45impl Default for BobyqaParams {
46    /// `xtol_rel = 1e-6` and `maxeval = 1000`, as `nloptr::nl.opts` sets them.
47    fn default() -> Self {
48        Self {
49            xtol_rel: 1e-6,
50            maxeval: 1000,
51        }
52    }
53}
54
55/// Why a search stopped, NLopt's codes.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum BobyqaStatus {
58    /// Still running.
59    Running,
60    /// The trust region shrank to `rhoend`: NLopt's `XTOL_REACHED` (4).
61    XtolReached,
62    /// The evaluation budget ran out: `MAXEVAL_REACHED` (5).
63    MaxevalReached,
64    /// Rounding errors stopped progress: `ROUNDOFF_LIMITED` (-4).
65    RoundoffLimited,
66    /// Plain `SUCCESS` (1), when the trust-region loop ends without one of the
67    /// above.
68    Success,
69}
70
71/// The finished search.
72#[derive(Clone, Debug)]
73pub struct BobyqaResult {
74    /// Best point, on the caller's scale.
75    pub x: Vec<f64>,
76    /// Objective there.
77    pub f: f64,
78    /// Objective evaluations.
79    pub evaluations: usize,
80    /// Why the search stopped.
81    pub status: BobyqaStatus,
82}
83
84///////////
85// Label //
86///////////
87
88/// Where [`BobyqaStepper::advance`] resumes, Powell's statement labels.
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90enum Label {
91    /// `prelim`, label 50: the next initial point.
92    PrelimNext,
93    /// `prelim`, after the value of that point.
94    PrelimPost,
95    /// `bobyqb` right after `prelim` returns.
96    Start,
97    /// Label 20: refresh `gopt` if `kopt` moved.
98    L20,
99    /// Label 60: trust-region step.
100    L60,
101    /// Label 90: shift `xbase` if `xopt` drifted.
102    L90,
103    /// Label 190: call `rescue`.
104    L190,
105    /// After `rescue` returns.
106    L190Post,
107    /// Label 210: alternative step by `altmov`.
108    L210,
109    /// Label 230: `vlag` and `beta` for the step.
110    L230,
111    /// Label 360: evaluate the objective at `xnew`.
112    L360,
113    /// After that value.
114    L360Post,
115    /// Label 650: is an interpolation point too far away?
116    L650,
117    /// Label 680: next `rho`.
118    L680,
119    /// Label 720: finish.
120    L720,
121    /// `rescue` up to label 260, which needs no values.
122    RescueStart,
123    /// `rescue`, label 260 loop: the next provisional point.
124    RescueNext,
125    /// `rescue`, after the value of that point.
126    RescuePost,
127    /// Waiting for a value; `tell` resumes at the stored label.
128    Wait,
129    /// Finished.
130    Done,
131}
132
133/////////////
134// Stepper //
135/////////////
136
137/// [`bobyqa`] as a reverse-communication state machine.
138///
139/// [`Self::ask`] gives the next point, [`Self::tell`] its value. The arithmetic
140/// is NLopt's, so for identical values the sequence of points is NLopt's.
141pub struct BobyqaStepper {
142    /// Number of variables.
143    n: usize,
144    /// Interpolation points, `2n + 1`.
145    npt: usize,
146    /// `npt + n`.
147    ndim: usize,
148    /// NLopt's rescaling: the stepper works in `x / s`.
149    s: Vec<f64>,
150    /// Bounds on the rescaled variables, one-based.
151    xl: Vec<f64>,
152    /// See [`Self::xl`].
153    xu: Vec<f64>,
154    /// Final trust-region radius.
155    rhoend: f64,
156    /// Initial trust-region radius.
157    rhobeg: f64,
158    /// Evaluation budget.
159    maxeval: usize,
160    /// Evaluations so far, NLopt's `nevals`.
161    nevals: usize,
162
163    /// The point being evaluated, rescaled, one-based.
164    x: Vec<f64>,
165    /// The same point on the caller's scale, zero-based, for [`Self::ask`].
166    pending: Vec<f64>,
167    /// Where to resume once the pending value arrives.
168    resume: Label,
169    /// The current label.
170    label: Label,
171    /// Last value told.
172    f: f64,
173    /// Exit status.
174    rc: BobyqaStatus,
175    /// Best value at the end.
176    minf: f64,
177    /// Best point at the end, rescaled, one-based.
178    xbest: Vec<f64>,
179
180    // Powell's arrays: one-dimensional ones one-based, matrices column-major.
181    /// Shift of origin.
182    xbase: Vec<f64>,
183    /// Interpolation points relative to `xbase`, `npt * n`.
184    xpt: Vec<f64>,
185    /// Values at the interpolation points.
186    fval: Vec<f64>,
187    /// Trust-region centre relative to `xbase`.
188    xopt: Vec<f64>,
189    /// Model gradient at `xopt`.
190    gopt: Vec<f64>,
191    /// Explicit second derivatives, packed.
192    hq: Vec<f64>,
193    /// Implicit second derivative parameters.
194    pq: Vec<f64>,
195    /// Last `n` columns of `H`, `ndim * n`.
196    bmat: Vec<f64>,
197    /// Factor of the leading `npt` block of `H`, `npt * (npt - n - 1)`.
198    zmat: Vec<f64>,
199    /// `xl - xbase`.
200    sl: Vec<f64>,
201    /// `xu - xbase`.
202    su: Vec<f64>,
203    /// Next point relative to `xbase`.
204    xnew: Vec<f64>,
205    /// Alternative to `xnew` from `altmov`.
206    xalt: Vec<f64>,
207    /// Trial step.
208    d: Vec<f64>,
209    /// Lagrange function values, `ndim`.
210    vlag: Vec<f64>,
211    /// Work space, one-based.
212    w: Vec<f64>,
213
214    // bobyqb's scalars, kept across pauses.
215    /// Index of the best interpolation point.
216    kopt: usize,
217    /// Index of the point to replace.
218    knew: usize,
219    /// `kopt` at the last `gopt` refresh.
220    kbase: usize,
221    /// Trust-region iterations since the last alternative step, or -1.
222    ntrits: i64,
223    /// `nevals` at the last `rescue`.
224    nresc: usize,
225    /// `nevals` at the last improvement test.
226    nfsav: usize,
227    /// Consecutive iterations where the Frobenius model looked better.
228    itest: i64,
229    /// Recent model errors.
230    diffa: f64,
231    /// See [`Self::diffa`].
232    diffb: f64,
233    /// See [`Self::diffa`].
234    diffc: f64,
235    /// Lower bound on the trust-region radius.
236    rho: f64,
237    /// Trust-region radius.
238    delta: f64,
239    /// `|xopt|^2`.
240    xoptsq: f64,
241    /// Squared step length from `trsbox`.
242    dsq: f64,
243    /// Least curvature seen by `trsbox`.
244    crvmin: f64,
245    /// Step length.
246    dnorm: f64,
247    /// Bound on the alternative step.
248    adelt: f64,
249    /// Squared distance threshold at label 650.
250    distsq: f64,
251    /// From `altmov`.
252    alpha: f64,
253    /// From `altmov`.
254    cauchy: f64,
255    /// Updating parameter.
256    beta: f64,
257    /// Updating denominator.
258    denom: f64,
259    /// Predicted change of the model.
260    vquad: f64,
261    /// Actual over predicted reduction.
262    ratio: f64,
263    /// `fval[kopt]` before the step.
264    fopt: f64,
265    /// Value at the first point, `fsave` in the C.
266    fsave: f64,
267
268    // prelim's state.
269    /// Points evaluated by `prelim` so far.
270    nf: usize,
271    /// `nf - 1`.
272    nfm: usize,
273    /// `nf - 1 - n`, may be negative.
274    nfx: i64,
275    /// First step along a coordinate.
276    stepa: f64,
277    /// Second step along a coordinate.
278    stepb: f64,
279    /// Value at the first point.
280    fbeg: f64,
281    /// Coordinate pair of an off-diagonal point.
282    ipt: usize,
283    /// See [`Self::ipt`].
284    jpt: usize,
285
286    // rescue's state.
287    /// Point index in the label 260 loop.
288    kpt: usize,
289    /// Model value at the rescue point.
290    rvquad: f64,
291    /// `fval[kopt]` on entry to `rescue`.
292    fbase: f64,
293    /// Rescue coordinates of the provisional point.
294    rip: usize,
295    /// See [`Self::rip`].
296    riq: usize,
297    /// Rescue step along `rip`.
298    xp: f64,
299    /// Rescue step along `riq`.
300    xq: f64,
301}
302
303impl BobyqaStepper {
304    /// Sets up the search as `nlopt_optimize` and `bobyqa` do, and asks for the
305    /// first point.
306    ///
307    /// ### Params
308    ///
309    /// * `x0` - Starting point, inside the box
310    /// * `lower` - Lower bounds, finite
311    /// * `upper` - Upper bounds, finite and above `lower`
312    /// * `params` - Stopping knobs, or nloptr's defaults
313    ///
314    /// ### Returns
315    ///
316    /// The stepper, or [`EdgeErrors::InvalidArgument`] when the box is too
317    /// narrow for the initial radius, as NLopt refuses it.
318    pub fn new(
319        x0: &[f64],
320        lower: &[f64],
321        upper: &[f64],
322        params: Option<BobyqaParams>,
323    ) -> Result<Self, EdgeErrors> {
324        let params = params.unwrap_or_default();
325        let n = x0.len();
326        if n == 0 || lower.len() != n || upper.len() != n {
327            return Err(EdgeErrors::InvalidArgument(
328                "BOBYQA needs matching, non-empty x0 and bounds.".to_string(),
329            ));
330        }
331
332        // nlopt_set_default_initial_step.
333        let mut dx = vec![0.0; n];
334        for i in 0..n {
335            let (lb, ub, x) = (lower[i], upper[i], x0[i]);
336            let mut step = f64::INFINITY;
337            if ub.is_finite() && lb.is_finite() && (ub - lb) * 0.25 < step && ub > lb {
338                step = (ub - lb) * 0.25;
339            }
340            if ub.is_finite() && ub - x < step && ub > x {
341                step = (ub - x) * 0.75;
342            }
343            if lb.is_finite() && x - lb < step && x > lb {
344                step = (x - lb) * 0.75;
345            }
346            if step.is_infinite() {
347                if ub.is_finite() && (ub - x).abs() < step.abs() {
348                    step = (ub - x) * 1.1;
349                }
350                if lb.is_finite() && (x - lb).abs() < step.abs() {
351                    step = (x - lb) * 1.1;
352                }
353            }
354            if step.is_infinite() || step == 0.0 || step.is_subnormal() {
355                step = x;
356            }
357            if step.is_infinite() || step == 0.0 {
358                step = 1.0;
359            }
360            dx[i] = step;
361        }
362
363        // nlopt_compute_rescaling: equal steps after rescaling by dx / dx[0].
364        let mut s = vec![1.0; n];
365        if n > 1 && (1..n).any(|i| dx[i] != dx[i - 1]) {
366            for i in 1..n {
367                s[i] = dx[i] / dx[0];
368            }
369        }
370        if s.iter().any(|&v| v == 0.0 || !v.is_finite()) {
371            return Err(EdgeErrors::InvalidArgument(
372                "BOBYQA rescaling overflowed.".to_string(),
373            ));
374        }
375
376        let one_based = |v: &[f64], scale: &[f64]| -> Vec<f64> {
377            std::iter::once(0.0)
378                .chain(v.iter().zip(scale).map(|(a, b)| a / b))
379                .collect()
380        };
381        let mut x = one_based(x0, &s);
382        let mut xl = one_based(lower, &s);
383        let mut xu = one_based(upper, &s);
384        for j in 1..=n {
385            if xl[j] > xu[j] {
386                std::mem::swap(&mut xl[j], &mut xu[j]);
387            }
388        }
389        let rhobeg = (dx[0] / s[0]).abs();
390        let rhoend = params.xtol_rel * rhobeg;
391
392        let npt = 2 * n + 1;
393        let np = n + 1;
394        let ndim = npt + n;
395        let mut sl = vec![0.0; n + 1];
396        let mut su = vec![0.0; n + 1];
397        for j in 1..=n {
398            let temp = xu[j] - xl[j];
399            if temp < rhobeg + rhobeg {
400                return Err(EdgeErrors::InvalidArgument(format!(
401                    "insufficient space between the bounds: {} - {} < {}",
402                    xu[j],
403                    xl[j],
404                    rhobeg + rhobeg
405                )));
406            }
407            sl[j] = xl[j] - x[j];
408            su[j] = xu[j] - x[j];
409            if sl[j] >= -rhobeg {
410                if sl[j] >= 0.0 {
411                    x[j] = xl[j];
412                    sl[j] = 0.0;
413                    su[j] = temp;
414                } else {
415                    x[j] = xl[j] + rhobeg;
416                    sl[j] = -rhobeg;
417                    su[j] = (xu[j] - x[j]).max(rhobeg);
418                }
419            } else if su[j] <= rhobeg {
420                if su[j] <= 0.0 {
421                    x[j] = xu[j];
422                    sl[j] = -temp;
423                    su[j] = 0.0;
424                } else {
425                    x[j] = xu[j] - rhobeg;
426                    sl[j] = (xl[j] - x[j]).min(-rhobeg);
427                    su[j] = rhobeg;
428                }
429            }
430        }
431
432        let mut stepper = Self {
433            n,
434            npt,
435            ndim,
436            s,
437            xl,
438            xu,
439            rhoend,
440            rhobeg,
441            maxeval: params.maxeval,
442            nevals: 0,
443            x,
444            pending: vec![0.0; n],
445            resume: Label::Done,
446            label: Label::PrelimNext,
447            f: 0.0,
448            rc: BobyqaStatus::Running,
449            minf: f64::INFINITY,
450            xbest: vec![0.0; n + 1],
451            xbase: vec![0.0; n + 1],
452            xpt: vec![0.0; npt * n],
453            fval: vec![0.0; npt + 1],
454            xopt: vec![0.0; n + 1],
455            gopt: vec![0.0; n + 1],
456            hq: vec![0.0; n * np / 2 + 1],
457            pq: vec![0.0; npt + 1],
458            bmat: vec![0.0; ndim * n],
459            zmat: vec![0.0; npt * (npt - np)],
460            sl,
461            su,
462            xnew: vec![0.0; n + 1],
463            xalt: vec![0.0; n + 1],
464            d: vec![0.0; n + 1],
465            vlag: vec![0.0; ndim + 1],
466            w: vec![0.0; 3 * ndim + n + npt + 1],
467            kopt: 1,
468            knew: 0,
469            kbase: 1,
470            ntrits: 0,
471            nresc: 0,
472            nfsav: 0,
473            itest: 0,
474            diffa: 0.0,
475            diffb: 0.0,
476            diffc: 0.0,
477            rho: rhobeg,
478            delta: rhobeg,
479            xoptsq: 0.0,
480            dsq: 0.0,
481            crvmin: 0.0,
482            dnorm: 0.0,
483            adelt: 0.0,
484            distsq: 0.0,
485            alpha: 0.0,
486            cauchy: 0.0,
487            beta: 0.0,
488            denom: 0.0,
489            vquad: 0.0,
490            ratio: 0.0,
491            fopt: 0.0,
492            fsave: 0.0,
493            nf: 0,
494            nfm: 0,
495            nfx: 0,
496            stepa: 0.0,
497            stepb: 0.0,
498            fbeg: 0.0,
499            ipt: 0,
500            jpt: 0,
501            kpt: 0,
502            rvquad: 0.0,
503            fbase: 0.0,
504            rip: 0,
505            riq: 0,
506            xp: 0.0,
507            xq: 0.0,
508        };
509        stepper.prelim_init();
510        stepper.advance();
511        Ok(stepper)
512    }
513
514    /// The point the search wants evaluated next.
515    ///
516    /// ### Returns
517    ///
518    /// The point on the caller's scale, or `None` once the search has finished.
519    pub fn ask(&self) -> Option<&[f64]> {
520        if self.label == Label::Wait {
521            Some(&self.pending)
522        } else {
523            None
524        }
525    }
526
527    /// Hands the stepper the value at the point [`Self::ask`] returned.
528    ///
529    /// ### Params
530    ///
531    /// * `value` - The objective there
532    pub fn tell(&mut self, value: f64) {
533        debug_assert_eq!(self.label, Label::Wait);
534        self.f = value;
535        self.label = self.resume;
536        self.advance();
537    }
538
539    /// The result, once [`Self::ask`] returns `None`.
540    ///
541    /// ### Returns
542    ///
543    /// The best point on the caller's scale, its value, the evaluation count
544    /// and the status.
545    pub fn result(&self) -> BobyqaResult {
546        BobyqaResult {
547            x: (1..=self.n)
548                .map(|j| self.xbest[j] * self.s[j - 1])
549                .collect(),
550            f: self.minf,
551            evaluations: self.nevals,
552            status: self.rc,
553        }
554    }
555
556    /// Pauses for the objective at `self.x`, resuming at `next`.
557    ///
558    /// ### Params
559    ///
560    /// * `next` - Label to resume at
561    fn request(&mut self, next: Label) {
562        for j in 1..=self.n {
563            self.pending[j - 1] = self.x[j] * self.s[j - 1];
564        }
565        self.nevals += 1;
566        self.resume = next;
567        self.label = Label::Wait;
568    }
569
570    /// Whether the evaluation budget is spent, `nlopt_stop_evals`.
571    ///
572    /// ### Returns
573    ///
574    /// `true` once `maxeval` evaluations have been made.
575    fn stop_evals(&self) -> bool {
576        self.maxeval > 0 && self.nevals >= self.maxeval
577    }
578
579    /// Index into `xpt`, one-based `(k, j)`.
580    #[inline]
581    fn ix(&self, k: usize, j: usize) -> usize {
582        (k - 1) + (j - 1) * self.npt
583    }
584
585    /// Index into `bmat`, one-based `(k, j)`.
586    #[inline]
587    fn ib(&self, k: usize, j: usize) -> usize {
588        (k - 1) + (j - 1) * self.ndim
589    }
590
591    /// Index into `zmat`, one-based `(k, j)`.
592    #[inline]
593    fn iz(&self, k: usize, j: usize) -> usize {
594        (k - 1) + (j - 1) * self.npt
595    }
596
597    /// `prelim` up to its loop: `xbase` and the zeroed model.
598    fn prelim_init(&mut self) {
599        for j in 1..=self.n {
600            self.xbase[j] = self.x[j];
601        }
602        self.xpt.fill(0.0);
603        self.bmat.fill(0.0);
604        self.hq.fill(0.0);
605        self.pq.fill(0.0);
606        self.zmat.fill(0.0);
607        self.nf = 0;
608    }
609
610    /// Runs from the current label until the next pause or the end.
611    fn advance(&mut self) {
612        loop {
613            self.label = match self.label {
614                Label::PrelimNext => self.prelim_next(),
615                Label::PrelimPost => self.prelim_post(),
616                Label::Start => self.start(),
617                Label::L20 => self.l20(),
618                Label::L60 => self.l60(),
619                Label::L90 => self.l90(),
620                Label::L190 => {
621                    self.nfsav = self.nevals;
622                    self.kbase = self.kopt;
623                    Label::RescueStart
624                }
625                Label::L190Post => self.l190_post(),
626                Label::L210 => self.l210(),
627                Label::L230 => self.l230(),
628                Label::L360 => self.l360(),
629                Label::L360Post => self.l360_post(),
630                Label::L650 => self.l650(),
631                Label::L680 => self.l680(),
632                Label::L720 => {
633                    self.l720();
634                    Label::Done
635                }
636                Label::RescueStart => self.rescue_start(),
637                Label::RescueNext => self.rescue_next(),
638                Label::RescuePost => self.rescue_post(),
639                Label::Wait | Label::Done => return,
640            };
641        }
642    }
643
644    ////////////
645    // prelim //
646    ////////////
647
648    /// `prelim`, label 50: places the next initial point and asks for it.
649    fn prelim_next(&mut self) -> Label {
650        let n = self.n;
651        let np = n + 1;
652        self.nfm = self.nf;
653        self.nfx = self.nf as i64 - n as i64;
654        self.nf += 1;
655        let nf = self.nf;
656        let nfm = self.nfm;
657        let rhobeg = self.rhobeg;
658        if nfm <= 2 * n {
659            if nfm >= 1 && nfm <= n {
660                self.stepa = rhobeg;
661                if self.su[nfm] == 0.0 {
662                    self.stepa = -self.stepa;
663                }
664                let i = self.ix(nf, nfm);
665                self.xpt[i] = self.stepa;
666            } else if nfm > n {
667                let nfx = self.nfx as usize;
668                self.stepa = self.xpt[self.ix(nf - n, nfx)];
669                self.stepb = -rhobeg;
670                if self.sl[nfx] == 0.0 {
671                    self.stepb = (2.0 * rhobeg).min(self.su[nfx]);
672                }
673                if self.su[nfx] == 0.0 {
674                    self.stepb = (-2.0 * rhobeg).max(self.sl[nfx]);
675                }
676                let i = self.ix(nf, nfx);
677                self.xpt[i] = self.stepb;
678            }
679        } else {
680            let mut itemp = (nfm - np) / n;
681            let mut jpt = nfm - itemp * n - n;
682            let mut ipt = jpt + itemp;
683            if ipt > n {
684                itemp = jpt;
685                jpt = ipt - n;
686                ipt = itemp;
687            }
688            self.ipt = ipt;
689            self.jpt = jpt;
690            let a = self.xpt[self.ix(ipt + 1, ipt)];
691            let i = self.ix(nf, ipt);
692            self.xpt[i] = a;
693            let b = self.xpt[self.ix(jpt + 1, jpt)];
694            let i = self.ix(nf, jpt);
695            self.xpt[i] = b;
696        }
697
698        for j in 1..=n {
699            let p = self.xpt[self.ix(nf, j)];
700            self.x[j] = self.xl[j].max(self.xbase[j] + p).min(self.xu[j]);
701            if p == self.sl[j] {
702                self.x[j] = self.xl[j];
703            }
704            if p == self.su[j] {
705                self.x[j] = self.xu[j];
706            }
707        }
708        self.request(Label::PrelimPost);
709        Label::Wait
710    }
711
712    /// `prelim` after the value of point `nf`.
713    fn prelim_post(&mut self) -> Label {
714        let n = self.n;
715        let npt = self.npt;
716        let nf = self.nf;
717        let nfm = self.nfm;
718        let f = self.f;
719        let rhosq = self.rhobeg * self.rhobeg;
720        let recip = 1.0 / rhosq;
721        self.fval[nf] = f;
722        if nf == 1 {
723            self.fbeg = f;
724            self.kopt = 1;
725        } else if f < self.fval[self.kopt] {
726            self.kopt = nf;
727        }
728
729        if nf <= 2 * n + 1 {
730            if nf >= 2 && nf <= n + 1 {
731                self.gopt[nfm] = (f - self.fbeg) / self.stepa;
732                if npt < nf + n {
733                    let (a, b, c) = (self.ib(1, nfm), self.ib(nf, nfm), self.ib(npt + nfm, nfm));
734                    self.bmat[a] = -1.0 / self.stepa;
735                    self.bmat[b] = 1.0 / self.stepa;
736                    self.bmat[c] = -0.5 * rhosq;
737                }
738            } else if nf >= n + 2 {
739                let nfx = self.nfx as usize;
740                let ih = nfx * (nfx + 1) / 2;
741                let temp = (f - self.fbeg) / self.stepb;
742                let diff = self.stepb - self.stepa;
743                self.hq[ih] = 2.0 * (temp - self.gopt[nfx]) / diff;
744                self.gopt[nfx] = (self.gopt[nfx] * self.stepb - temp * self.stepa) / diff;
745                if self.stepa * self.stepb < 0.0 && f < self.fval[nf - n] {
746                    self.fval[nf] = self.fval[nf - n];
747                    self.fval[nf - n] = f;
748                    if self.kopt == nf {
749                        self.kopt = nf - n;
750                    }
751                    let (a, b) = (self.ix(nf - n, nfx), self.ix(nf, nfx));
752                    self.xpt[a] = self.stepb;
753                    self.xpt[b] = self.stepa;
754                }
755                let b1 = self.ib(1, nfx);
756                let bnf = self.ib(nf, nfx);
757                let bnn = self.ib(nf - n, nfx);
758                self.bmat[b1] = -(self.stepa + self.stepb) / (self.stepa * self.stepb);
759                self.bmat[bnf] = -0.5 / self.xpt[self.ix(nf - n, nfx)];
760                self.bmat[bnn] = -self.bmat[b1] - self.bmat[bnf];
761                let z1 = self.iz(1, nfx);
762                let znf = self.iz(nf, nfx);
763                let znn = self.iz(nf - n, nfx);
764                self.zmat[z1] = 2.0f64.sqrt() / (self.stepa * self.stepb);
765                self.zmat[znf] = 0.5f64.sqrt() / rhosq;
766                self.zmat[znn] = -self.zmat[z1] - self.zmat[znf];
767            }
768        } else {
769            let nfx = self.nfx as usize;
770            let (ipt, jpt) = (self.ipt, self.jpt);
771            let ih = ipt * (ipt - 1) / 2 + jpt;
772            let idx = [
773                self.iz(1, nfx),
774                self.iz(nf, nfx),
775                self.iz(ipt + 1, nfx),
776                self.iz(jpt + 1, nfx),
777            ];
778            self.zmat[idx[0]] = recip;
779            self.zmat[idx[1]] = recip;
780            self.zmat[idx[2]] = -recip;
781            self.zmat[idx[3]] = -recip;
782            let temp = self.xpt[self.ix(nf, ipt)] * self.xpt[self.ix(nf, jpt)];
783            self.hq[ih] = (self.fbeg - self.fval[ipt + 1] - self.fval[jpt + 1] + f) / temp;
784        }
785        if self.stop_evals() {
786            self.rc = BobyqaStatus::MaxevalReached;
787            return self.after_prelim();
788        }
789        if nf < npt {
790            return Label::PrelimNext;
791        }
792        self.after_prelim()
793    }
794
795    /// `bobyqb` right after `prelim`: sets `xopt`, branching to the end when
796    /// `prelim` stopped early.
797    fn after_prelim(&mut self) -> Label {
798        self.xoptsq = 0.0;
799        for i in 1..=self.n {
800            self.xopt[i] = self.xpt[self.ix(self.kopt, i)];
801            self.xoptsq += self.xopt[i] * self.xopt[i];
802        }
803        self.fsave = self.fval[1];
804        if self.rc != BobyqaStatus::Running {
805            return Label::L720;
806        }
807        Label::Start
808    }
809
810    ////////////
811    // bobyqb //
812    ////////////
813
814    /// The settings before the first iteration.
815    fn start(&mut self) -> Label {
816        self.kbase = 1;
817        self.rho = self.rhobeg;
818        self.delta = self.rho;
819        self.nresc = self.nevals;
820        self.ntrits = 0;
821        self.diffa = 0.0;
822        self.diffb = 0.0;
823        self.itest = 0;
824        self.nfsav = self.nevals;
825        Label::L20
826    }
827
828    /// Label 20: moves `gopt` to `xopt` if `kopt` changed.
829    fn l20(&mut self) -> Label {
830        let n = self.n;
831        let npt = self.npt;
832        if self.kopt != self.kbase {
833            let mut ih = 0;
834            for j in 1..=n {
835                for i in 1..=j {
836                    ih += 1;
837                    if i < j {
838                        self.gopt[j] += self.hq[ih] * self.xopt[i];
839                    }
840                    self.gopt[i] += self.hq[ih] * self.xopt[j];
841                }
842            }
843            if self.nevals > npt {
844                for k in 1..=npt {
845                    let mut temp = 0.0;
846                    for j in 1..=n {
847                        temp += self.xpt[self.ix(k, j)] * self.xopt[j];
848                    }
849                    temp *= self.pq[k];
850                    for i in 1..=n {
851                        self.gopt[i] += temp * self.xpt[self.ix(k, i)];
852                    }
853                }
854            }
855        }
856        Label::L60
857    }
858
859    /// Label 60: the trust-region step, or the decision to shrink `rho`.
860    fn l60(&mut self) -> Label {
861        let n = self.n;
862        let npt = self.npt;
863        {
864            let (gnew, rest) = self.w[1..].split_at_mut(n);
865            let (xbdi, rest) = rest.split_at_mut(n);
866            let (s, rest) = rest.split_at_mut(n);
867            let (hs, rest) = rest.split_at_mut(n);
868            let hred = &mut rest[..n];
869            trsbox(
870                n,
871                npt,
872                &self.xpt,
873                &self.xopt[1..],
874                &self.gopt[1..],
875                &self.hq[1..],
876                &self.pq[1..],
877                &self.sl[1..],
878                &self.su[1..],
879                self.delta,
880                &mut self.xnew[1..],
881                &mut self.d[1..],
882                gnew,
883                xbdi,
884                s,
885                hs,
886                hred,
887                &mut self.dsq,
888                &mut self.crvmin,
889            );
890        }
891        self.dnorm = self.delta.min(self.dsq.sqrt());
892        if self.dnorm < 0.5 * self.rho {
893            self.ntrits = -1;
894            self.distsq = (10.0 * self.rho) * (10.0 * self.rho);
895            if self.nevals <= self.nfsav + 2 {
896                return Label::L650;
897            }
898            let errbig = self.diffa.max(self.diffb).max(self.diffc);
899            let frhosq = self.rho * 0.125 * self.rho;
900            if self.crvmin > 0.0 && errbig > frhosq * self.crvmin {
901                return Label::L650;
902            }
903            let bdtol = errbig / self.rho;
904            for j in 1..=n {
905                let mut bdtest = bdtol;
906                if self.xnew[j] == self.sl[j] {
907                    bdtest = self.w[j];
908                }
909                if self.xnew[j] == self.su[j] {
910                    bdtest = -self.w[j];
911                }
912                if bdtest < bdtol {
913                    let mut curv = self.hq[(j + j * j) / 2];
914                    for k in 1..=npt {
915                        let v = self.xpt[self.ix(k, j)];
916                        curv += self.pq[k] * (v * v);
917                    }
918                    bdtest += 0.5 * curv * self.rho;
919                    if bdtest < bdtol {
920                        return Label::L650;
921                    }
922                }
923            }
924            return Label::L680;
925        }
926        self.ntrits += 1;
927        Label::L90
928    }
929
930    /// Label 90: shifts `xbase` to `xopt` when the step is small against it.
931    fn l90(&mut self) -> Label {
932        let n = self.n;
933        let npt = self.npt;
934        let nptm = npt - n - 1;
935        if self.dsq <= self.xoptsq * 0.001 {
936            let fracsq = self.xoptsq * 0.25;
937            let mut sumpq = 0.0;
938            for k in 1..=npt {
939                sumpq += self.pq[k];
940                let mut sum = -0.5 * self.xoptsq;
941                for i in 1..=n {
942                    sum += self.xpt[self.ix(k, i)] * self.xopt[i];
943                }
944                self.w[npt + k] = sum;
945                let temp = fracsq - 0.5 * sum;
946                for i in 1..=n {
947                    self.w[i] = self.bmat[self.ib(k, i)];
948                    self.vlag[i] = sum * self.xpt[self.ix(k, i)] + temp * self.xopt[i];
949                    let ip = npt + i;
950                    for j in 1..=i {
951                        let b = self.ib(ip, j);
952                        self.bmat[b] =
953                            self.bmat[b] + self.w[i] * self.vlag[j] + self.vlag[i] * self.w[j];
954                    }
955                }
956            }
957
958            for jj in 1..=nptm {
959                let mut sumz = 0.0;
960                let mut sumw = 0.0;
961                for k in 1..=npt {
962                    let z = self.zmat[self.iz(k, jj)];
963                    sumz += z;
964                    self.vlag[k] = self.w[npt + k] * z;
965                    sumw += self.vlag[k];
966                }
967                for j in 1..=n {
968                    let mut sum = (fracsq * sumz - 0.5 * sumw) * self.xopt[j];
969                    for k in 1..=npt {
970                        sum += self.vlag[k] * self.xpt[self.ix(k, j)];
971                    }
972                    self.w[j] = sum;
973                    for k in 1..=npt {
974                        let b = self.ib(k, j);
975                        self.bmat[b] += sum * self.zmat[self.iz(k, jj)];
976                    }
977                }
978                for i in 1..=n {
979                    let ip = i + npt;
980                    let temp = self.w[i];
981                    for j in 1..=i {
982                        let b = self.ib(ip, j);
983                        self.bmat[b] += temp * self.w[j];
984                    }
985                }
986            }
987
988            let mut ih = 0;
989            for j in 1..=n {
990                self.w[j] = -0.5 * sumpq * self.xopt[j];
991                for k in 1..=npt {
992                    self.w[j] += self.pq[k] * self.xpt[self.ix(k, j)];
993                    let x = self.ix(k, j);
994                    self.xpt[x] -= self.xopt[j];
995                }
996                for i in 1..=j {
997                    ih += 1;
998                    self.hq[ih] = self.hq[ih] + self.w[i] * self.xopt[j] + self.xopt[i] * self.w[j];
999                    let (dst, src) = (self.ib(npt + i, j), self.ib(npt + j, i));
1000                    self.bmat[dst] = self.bmat[src];
1001                }
1002            }
1003            for i in 1..=n {
1004                self.xbase[i] += self.xopt[i];
1005                self.xnew[i] -= self.xopt[i];
1006                self.sl[i] -= self.xopt[i];
1007                self.su[i] -= self.xopt[i];
1008                self.xopt[i] = 0.0;
1009            }
1010            self.xoptsq = 0.0;
1011        }
1012        if self.ntrits == 0 {
1013            Label::L210
1014        } else {
1015            Label::L230
1016        }
1017    }
1018
1019    /// After `rescue`: the branch the C takes on its return.
1020    fn l190_post(&mut self) -> Label {
1021        let n = self.n;
1022        self.xoptsq = 0.0;
1023        if self.kopt != self.kbase {
1024            for i in 1..=n {
1025                self.xopt[i] = self.xpt[self.ix(self.kopt, i)];
1026                self.xoptsq += self.xopt[i] * self.xopt[i];
1027            }
1028        }
1029        if self.rc != BobyqaStatus::Running {
1030            return Label::L720;
1031        }
1032        self.nresc = self.nevals;
1033        if self.nfsav < self.nevals {
1034            self.nfsav = self.nevals;
1035            return Label::L20;
1036        }
1037        if self.ntrits > 0 {
1038            return Label::L60;
1039        }
1040        Label::L210
1041    }
1042
1043    /// Label 210: the alternative step of `altmov`.
1044    fn l210(&mut self) -> Label {
1045        let n = self.n;
1046        let npt = self.npt;
1047        let ndim = self.ndim;
1048        {
1049            let (head, tail) = self.w.split_at_mut(ndim + 1);
1050            let (glag, hcol) = head[1..].split_at_mut(n);
1051            let hcol = &mut hcol[..npt];
1052            let wk = &mut tail[..2 * n];
1053            altmov(
1054                n,
1055                npt,
1056                &self.xpt,
1057                &self.xopt[1..],
1058                &self.bmat,
1059                &self.zmat,
1060                ndim,
1061                &self.sl[1..],
1062                &self.su[1..],
1063                self.kopt,
1064                self.knew,
1065                self.adelt,
1066                &mut self.xnew[1..],
1067                &mut self.xalt[1..],
1068                &mut self.alpha,
1069                &mut self.cauchy,
1070                glag,
1071                hcol,
1072                wk,
1073            );
1074        }
1075        for i in 1..=n {
1076            self.d[i] = self.xnew[i] - self.xopt[i];
1077        }
1078        Label::L230
1079    }
1080
1081    /// Label 230: `vlag` and `beta` for the step, and the choice of `knew`.
1082    fn l230(&mut self) -> Label {
1083        let n = self.n;
1084        let npt = self.npt;
1085        let nptm = npt - n - 1;
1086        for k in 1..=npt {
1087            let mut suma = 0.0;
1088            let mut sumb = 0.0;
1089            let mut sum = 0.0;
1090            for j in 1..=n {
1091                suma += self.xpt[self.ix(k, j)] * self.d[j];
1092                sumb += self.xpt[self.ix(k, j)] * self.xopt[j];
1093                sum += self.bmat[self.ib(k, j)] * self.d[j];
1094            }
1095            self.w[k] = suma * (0.5 * suma + sumb);
1096            self.vlag[k] = sum;
1097            self.w[npt + k] = suma;
1098        }
1099        self.beta = 0.0;
1100        for jj in 1..=nptm {
1101            let mut sum = 0.0;
1102            for k in 1..=npt {
1103                sum += self.zmat[self.iz(k, jj)] * self.w[k];
1104            }
1105            self.beta -= sum * sum;
1106            for k in 1..=npt {
1107                self.vlag[k] += sum * self.zmat[self.iz(k, jj)];
1108            }
1109        }
1110        self.dsq = 0.0;
1111        let mut bsum = 0.0;
1112        let mut dx = 0.0;
1113        for j in 1..=n {
1114            self.dsq += self.d[j] * self.d[j];
1115            let mut sum = 0.0;
1116            for k in 1..=npt {
1117                sum += self.w[k] * self.bmat[self.ib(k, j)];
1118            }
1119            bsum += sum * self.d[j];
1120            let jp = npt + j;
1121            for i in 1..=n {
1122                sum += self.bmat[self.ib(jp, i)] * self.d[i];
1123            }
1124            self.vlag[jp] = sum;
1125            bsum += sum * self.d[j];
1126            dx += self.d[j] * self.xopt[j];
1127        }
1128        self.beta =
1129            dx * dx + self.dsq * (self.xoptsq + dx + dx + 0.5 * self.dsq) + self.beta - bsum;
1130        self.vlag[self.kopt] += 1.0;
1131
1132        if self.ntrits == 0 {
1133            let vk = self.vlag[self.knew];
1134            self.denom = vk * vk + self.alpha * self.beta;
1135            if self.denom < self.cauchy && self.cauchy > 0.0 {
1136                for i in 1..=n {
1137                    self.xnew[i] = self.xalt[i];
1138                    self.d[i] = self.xnew[i] - self.xopt[i];
1139                }
1140                self.cauchy = 0.0;
1141                return Label::L230;
1142            }
1143            if self.denom <= 0.5 * (vk * vk) {
1144                if self.nevals > self.nresc {
1145                    return Label::L190;
1146                }
1147                self.rc = BobyqaStatus::RoundoffLimited;
1148                return Label::L720;
1149            }
1150        } else {
1151            let delsq = self.delta * self.delta;
1152            let mut scaden = 0.0;
1153            let mut biglsq = 0.0f64;
1154            self.knew = 0;
1155            for k in 1..=npt {
1156                if k == self.kopt {
1157                    continue;
1158                }
1159                let mut hdiag = 0.0;
1160                for jj in 1..=nptm {
1161                    let z = self.zmat[self.iz(k, jj)];
1162                    hdiag += z * z;
1163                }
1164                let den = self.beta * hdiag + self.vlag[k] * self.vlag[k];
1165                let mut distsq = 0.0;
1166                for j in 1..=n {
1167                    let v = self.xpt[self.ix(k, j)] - self.xopt[j];
1168                    distsq += v * v;
1169                }
1170                let r = distsq / delsq;
1171                let temp = 1.0f64.max(r * r);
1172                if temp * den > scaden {
1173                    scaden = temp * den;
1174                    self.knew = k;
1175                    self.denom = den;
1176                }
1177                biglsq = biglsq.max(temp * (self.vlag[k] * self.vlag[k]));
1178            }
1179            if scaden <= 0.5 * biglsq {
1180                if self.nevals > self.nresc {
1181                    return Label::L190;
1182                }
1183                self.rc = BobyqaStatus::RoundoffLimited;
1184                return Label::L720;
1185            }
1186        }
1187        Label::L360
1188    }
1189
1190    /// Label 360: asks for the objective at `xbase + xnew`.
1191    fn l360(&mut self) -> Label {
1192        for i in 1..=self.n {
1193            self.x[i] = self.xl[i].max(self.xbase[i] + self.xnew[i]).min(self.xu[i]);
1194            if self.xnew[i] == self.sl[i] {
1195                self.x[i] = self.xl[i];
1196            }
1197            if self.xnew[i] == self.su[i] {
1198                self.x[i] = self.xu[i];
1199            }
1200        }
1201        if self.stop_evals() {
1202            self.rc = BobyqaStatus::MaxevalReached;
1203            return Label::L720;
1204        }
1205        self.request(Label::L360Post);
1206        Label::Wait
1207    }
1208
1209    /// After the value at `xnew`: model error, radius, and the model update.
1210    fn l360_post(&mut self) -> Label {
1211        let n = self.n;
1212        let npt = self.npt;
1213        let nptm = npt - n - 1;
1214        let nh = n * (n + 1) / 2;
1215        let f = self.f;
1216        if self.ntrits == -1 {
1217            self.fsave = f;
1218            self.rc = BobyqaStatus::XtolReached;
1219            if self.fsave < self.fval[self.kopt] {
1220                self.minf = f;
1221                self.xbest.copy_from_slice(&self.x);
1222                return Label::Done;
1223            }
1224            return Label::L720;
1225        }
1226
1227        self.fopt = self.fval[self.kopt];
1228        let fopt = self.fopt;
1229        let mut vquad = 0.0;
1230        let mut ih = 0;
1231        for j in 1..=n {
1232            vquad += self.d[j] * self.gopt[j];
1233            for i in 1..=j {
1234                ih += 1;
1235                let mut temp = self.d[i] * self.d[j];
1236                if i == j {
1237                    temp *= 0.5;
1238                }
1239                vquad += self.hq[ih] * temp;
1240            }
1241        }
1242        for k in 1..=npt {
1243            let v = self.w[npt + k];
1244            vquad += 0.5 * self.pq[k] * (v * v);
1245        }
1246        self.vquad = vquad;
1247        let diff = f - fopt - vquad;
1248        self.diffc = self.diffb;
1249        self.diffb = self.diffa;
1250        self.diffa = diff.abs();
1251        if self.dnorm > self.rho {
1252            self.nfsav = self.nevals;
1253        }
1254
1255        if self.ntrits > 0 {
1256            if vquad >= 0.0 {
1257                self.rc = BobyqaStatus::RoundoffLimited;
1258                return Label::L720;
1259            }
1260            self.ratio = (f - fopt) / vquad;
1261            if self.ratio <= 0.1 {
1262                self.delta = (0.5 * self.delta).min(self.dnorm);
1263            } else if self.ratio <= 0.7 {
1264                self.delta = (0.5 * self.delta).max(self.dnorm);
1265            } else {
1266                self.delta = (0.5 * self.delta).max(self.dnorm + self.dnorm);
1267            }
1268            if self.delta <= self.rho * 1.5 {
1269                self.delta = self.rho;
1270            }
1271
1272            if f < fopt {
1273                let ksav = self.knew;
1274                let densav = self.denom;
1275                let delsq = self.delta * self.delta;
1276                let mut scaden = 0.0;
1277                let mut biglsq = 0.0f64;
1278                self.knew = 0;
1279                for k in 1..=npt {
1280                    let mut hdiag = 0.0;
1281                    for jj in 1..=nptm {
1282                        let z = self.zmat[self.iz(k, jj)];
1283                        hdiag += z * z;
1284                    }
1285                    let den = self.beta * hdiag + self.vlag[k] * self.vlag[k];
1286                    let mut distsq = 0.0;
1287                    for j in 1..=n {
1288                        let v = self.xpt[self.ix(k, j)] - self.xnew[j];
1289                        distsq += v * v;
1290                    }
1291                    let r = distsq / delsq;
1292                    let temp = 1.0f64.max(r * r);
1293                    if temp * den > scaden {
1294                        scaden = temp * den;
1295                        self.knew = k;
1296                        self.denom = den;
1297                    }
1298                    biglsq = biglsq.max(temp * (self.vlag[k] * self.vlag[k]));
1299                }
1300                if scaden <= 0.5 * biglsq {
1301                    self.knew = ksav;
1302                    self.denom = densav;
1303                }
1304            }
1305        }
1306
1307        let knew = self.knew;
1308        update(
1309            n,
1310            npt,
1311            &mut self.bmat,
1312            &mut self.zmat,
1313            self.ndim,
1314            &mut self.vlag[1..],
1315            self.beta,
1316            self.denom,
1317            knew,
1318            &mut self.w[1..],
1319        );
1320        let mut ih = 0;
1321        let pqold = self.pq[knew];
1322        self.pq[knew] = 0.0;
1323        for i in 1..=n {
1324            let temp = pqold * self.xpt[self.ix(knew, i)];
1325            for j in 1..=i {
1326                ih += 1;
1327                self.hq[ih] += temp * self.xpt[self.ix(knew, j)];
1328            }
1329        }
1330        for jj in 1..=nptm {
1331            let temp = diff * self.zmat[self.iz(knew, jj)];
1332            for k in 1..=npt {
1333                self.pq[k] += temp * self.zmat[self.iz(k, jj)];
1334            }
1335        }
1336
1337        self.fval[knew] = f;
1338        for i in 1..=n {
1339            let x = self.ix(knew, i);
1340            self.xpt[x] = self.xnew[i];
1341            self.w[i] = self.bmat[self.ib(knew, i)];
1342        }
1343        for k in 1..=npt {
1344            let mut suma = 0.0;
1345            for jj in 1..=nptm {
1346                suma += self.zmat[self.iz(knew, jj)] * self.zmat[self.iz(k, jj)];
1347            }
1348            if suma.is_infinite() {
1349                self.rc = BobyqaStatus::RoundoffLimited;
1350                return Label::L720;
1351            }
1352            let mut sumb = 0.0;
1353            for j in 1..=n {
1354                sumb += self.xpt[self.ix(k, j)] * self.xopt[j];
1355            }
1356            let temp = suma * sumb;
1357            for i in 1..=n {
1358                self.w[i] += temp * self.xpt[self.ix(k, i)];
1359            }
1360        }
1361        for i in 1..=n {
1362            self.gopt[i] += diff * self.w[i];
1363        }
1364
1365        if f < fopt {
1366            self.kopt = knew;
1367            self.xoptsq = 0.0;
1368            let mut ih = 0;
1369            for j in 1..=n {
1370                self.xopt[j] = self.xnew[j];
1371                self.xoptsq += self.xopt[j] * self.xopt[j];
1372                for i in 1..=j {
1373                    ih += 1;
1374                    if i < j {
1375                        self.gopt[j] += self.hq[ih] * self.d[i];
1376                    }
1377                    self.gopt[i] += self.hq[ih] * self.d[j];
1378                }
1379            }
1380            for k in 1..=npt {
1381                let mut temp = 0.0;
1382                for j in 1..=n {
1383                    temp += self.xpt[self.ix(k, j)] * self.d[j];
1384                }
1385                temp *= self.pq[k];
1386                for i in 1..=n {
1387                    self.gopt[i] += temp * self.xpt[self.ix(k, i)];
1388                }
1389            }
1390            // `nlopt_stop_ftol` with nloptr's zero tolerances never fires.
1391        }
1392
1393        if self.ntrits > 0 {
1394            for k in 1..=npt {
1395                self.vlag[k] = self.fval[k] - self.fval[self.kopt];
1396                self.w[k] = 0.0;
1397            }
1398            for j in 1..=nptm {
1399                let mut sum = 0.0;
1400                for k in 1..=npt {
1401                    sum += self.zmat[self.iz(k, j)] * self.vlag[k];
1402                }
1403                for k in 1..=npt {
1404                    self.w[k] += sum * self.zmat[self.iz(k, j)];
1405                }
1406            }
1407            for k in 1..=npt {
1408                let mut sum = 0.0;
1409                for j in 1..=n {
1410                    sum += self.xpt[self.ix(k, j)] * self.xopt[j];
1411                }
1412                self.w[k + npt] = self.w[k];
1413                self.w[k] *= sum;
1414            }
1415            let mut gqsq = 0.0;
1416            let mut gisq = 0.0;
1417            for i in 1..=n {
1418                let mut sum = 0.0;
1419                for k in 1..=npt {
1420                    sum = sum
1421                        + self.bmat[self.ib(k, i)] * self.vlag[k]
1422                        + self.xpt[self.ix(k, i)] * self.w[k];
1423                }
1424                if self.xopt[i] == self.sl[i] {
1425                    let a = 0.0f64.min(self.gopt[i]);
1426                    gqsq += a * a;
1427                    let b = 0.0f64.min(sum);
1428                    gisq += b * b;
1429                } else if self.xopt[i] == self.su[i] {
1430                    let a = 0.0f64.max(self.gopt[i]);
1431                    gqsq += a * a;
1432                    let b = 0.0f64.max(sum);
1433                    gisq += b * b;
1434                } else {
1435                    gqsq += self.gopt[i] * self.gopt[i];
1436                    gisq += sum * sum;
1437                }
1438                self.vlag[npt + i] = sum;
1439            }
1440
1441            self.itest += 1;
1442            if gqsq < 10.0 * gisq {
1443                self.itest = 0;
1444            }
1445            if self.itest >= 3 {
1446                for i in 1..=npt.max(nh) {
1447                    if i <= n {
1448                        self.gopt[i] = self.vlag[npt + i];
1449                    }
1450                    if i <= npt {
1451                        self.pq[i] = self.w[npt + i];
1452                    }
1453                    if i <= nh {
1454                        self.hq[i] = 0.0;
1455                    }
1456                    self.itest = 0;
1457                }
1458            }
1459        }
1460
1461        if self.ntrits == 0 {
1462            return Label::L60;
1463        }
1464        if f <= fopt + 0.1 * vquad {
1465            return Label::L60;
1466        }
1467        let a = 2.0 * self.delta;
1468        let b = 10.0 * self.rho;
1469        self.distsq = (a * a).max(b * b);
1470        Label::L650
1471    }
1472
1473    /// Label 650: replaces the farthest interpolation point, or decides the
1474    /// work at this `rho` is done.
1475    fn l650(&mut self) -> Label {
1476        let n = self.n;
1477        let npt = self.npt;
1478        self.knew = 0;
1479        for k in 1..=npt {
1480            let mut sum = 0.0;
1481            for j in 1..=n {
1482                let v = self.xpt[self.ix(k, j)] - self.xopt[j];
1483                sum += v * v;
1484            }
1485            if sum > self.distsq {
1486                self.knew = k;
1487                self.distsq = sum;
1488            }
1489        }
1490
1491        if self.knew > 0 {
1492            let dist = self.distsq.sqrt();
1493            if self.ntrits == -1 {
1494                self.delta = (0.1 * self.delta).min(0.5 * dist);
1495                if self.delta <= self.rho * 1.5 {
1496                    self.delta = self.rho;
1497                }
1498            }
1499            self.ntrits = 0;
1500            self.adelt = (0.1 * dist).min(self.delta).max(self.rho);
1501            self.dsq = self.adelt * self.adelt;
1502            return Label::L90;
1503        }
1504        if self.ntrits == -1 {
1505            return Label::L680;
1506        }
1507        if self.ratio > 0.0 {
1508            return Label::L60;
1509        }
1510        if self.delta.max(self.dnorm) > self.rho {
1511            return Label::L60;
1512        }
1513        Label::L680
1514    }
1515
1516    /// Label 680: the next `rho`, or the final Newton step, or the end.
1517    fn l680(&mut self) -> Label {
1518        if self.rho > self.rhoend {
1519            self.delta = 0.5 * self.rho;
1520            self.ratio = self.rho / self.rhoend;
1521            if self.ratio <= 16.0 {
1522                self.rho = self.rhoend;
1523            } else if self.ratio <= 250.0 {
1524                self.rho = self.ratio.sqrt() * self.rhoend;
1525            } else {
1526                self.rho *= 0.1;
1527            }
1528            self.delta = self.delta.max(self.rho);
1529            self.ntrits = 0;
1530            self.nfsav = self.nevals;
1531            return Label::L60;
1532        }
1533        if self.ntrits == -1 {
1534            return Label::L360;
1535        }
1536        Label::L720
1537    }
1538
1539    /// Label 720: the best point, clamped onto the bounds it touches.
1540    fn l720(&mut self) {
1541        for i in 1..=self.n {
1542            self.x[i] = self.xl[i].max(self.xbase[i] + self.xopt[i]).min(self.xu[i]);
1543            if self.xopt[i] == self.sl[i] {
1544                self.x[i] = self.xl[i];
1545            }
1546            if self.xopt[i] == self.su[i] {
1547                self.x[i] = self.xu[i];
1548            }
1549        }
1550        self.minf = self.fval[self.kopt];
1551        self.xbest.copy_from_slice(&self.x);
1552        if self.rc == BobyqaStatus::Running {
1553            self.rc = BobyqaStatus::Success;
1554        }
1555    }
1556
1557    ////////////
1558    // rescue //
1559    ////////////
1560
1561    /// `ptsaux(r, j)` in bobyqb's work space, `r` 1 or 2.
1562    #[inline]
1563    fn pa(&self, r: usize, j: usize) -> usize {
1564        1 + (r - 1) + 2 * (j - 1)
1565    }
1566
1567    /// `ptsid(k)` in bobyqb's work space.
1568    #[inline]
1569    fn pid(&self, k: usize) -> usize {
1570        2 * self.n + k
1571    }
1572
1573    /// `rescue`'s own `w(i)` in bobyqb's work space.
1574    #[inline]
1575    fn rw(&self, i: usize) -> usize {
1576        self.ndim + self.n + i
1577    }
1578
1579    /// The integer parts `(p, q)` of a provisional point's identifier.
1580    ///
1581    /// ### Params
1582    ///
1583    /// * `k` - Point index
1584    ///
1585    /// ### Returns
1586    ///
1587    /// `ip = (int) ptsid(k)` and `iq = (int) (np * ptsid(k) - ip * np)`.
1588    fn pid_parts(&self, k: usize) -> (usize, usize) {
1589        let np = (self.n + 1) as f64;
1590        let v = self.w[self.pid(k)];
1591        let ip = v as i64;
1592        let iq = (np * v - (ip as f64) * np) as i64;
1593        (ip.max(0) as usize, iq.max(0) as usize)
1594    }
1595
1596    /// `rescue` up to label 260: new provisional points, `bmat` and `zmat`.
1597    fn rescue_start(&mut self) -> Label {
1598        let n = self.n;
1599        let npt = self.npt;
1600        let ndim = self.ndim;
1601        let np = n + 1;
1602        let nptm = npt - np;
1603        let sfrac = 0.5 / np as f64;
1604
1605        let mut sumpq = 0.0;
1606        let mut winc = 0.0f64;
1607        for k in 1..=npt {
1608            let mut distsq = 0.0;
1609            for j in 1..=n {
1610                let x = self.ix(k, j);
1611                self.xpt[x] -= self.xopt[j];
1612                distsq += self.xpt[x] * self.xpt[x];
1613            }
1614            sumpq += self.pq[k];
1615            let r = self.rw(ndim + k);
1616            self.w[r] = distsq;
1617            winc = winc.max(distsq);
1618            for j in 1..=nptm {
1619                let z = self.iz(k, j);
1620                self.zmat[z] = 0.0;
1621            }
1622        }
1623
1624        let mut ih = 0;
1625        for j in 1..=n {
1626            let rj = self.rw(j);
1627            self.w[rj] = 0.5 * sumpq * self.xopt[j];
1628            for k in 1..=npt {
1629                self.w[rj] += self.pq[k] * self.xpt[self.ix(k, j)];
1630            }
1631            for i in 1..=j {
1632                ih += 1;
1633                self.hq[ih] =
1634                    self.hq[ih] + self.w[self.rw(i)] * self.xopt[j] + self.w[rj] * self.xopt[i];
1635            }
1636        }
1637
1638        for j in 1..=n {
1639            self.xbase[j] += self.xopt[j];
1640            self.sl[j] -= self.xopt[j];
1641            self.su[j] -= self.xopt[j];
1642            self.xopt[j] = 0.0;
1643            let (p1, p2) = (self.pa(1, j), self.pa(2, j));
1644            self.w[p1] = self.delta.min(self.su[j]);
1645            self.w[p2] = (-self.delta).max(self.sl[j]);
1646            if self.w[p1] + self.w[p2] < 0.0 {
1647                self.w.swap(p1, p2);
1648            }
1649            if self.w[p2].abs() < 0.5 * self.w[p1].abs() {
1650                self.w[p2] = 0.5 * self.w[p1];
1651            }
1652            for i in 1..=ndim {
1653                let b = self.ib(i, j);
1654                self.bmat[b] = 0.0;
1655            }
1656        }
1657        self.fbase = self.fval[self.kopt];
1658
1659        let p1 = self.pid(1);
1660        self.w[p1] = sfrac;
1661        for j in 1..=n {
1662            let jp = j + 1;
1663            let jpn = jp + n;
1664            let pj = self.pid(jp);
1665            self.w[pj] = j as f64 + sfrac;
1666            let a1 = self.w[self.pa(1, j)];
1667            let a2 = self.w[self.pa(2, j)];
1668            if jpn <= npt {
1669                let pj = self.pid(jpn);
1670                self.w[pj] = j as f64 / np as f64 + sfrac;
1671                let temp = 1.0 / (a1 - a2);
1672                let (bjp, bjpn, b1) = (self.ib(jp, j), self.ib(jpn, j), self.ib(1, j));
1673                self.bmat[bjp] = -temp + 1.0 / a1;
1674                self.bmat[bjpn] = temp + 1.0 / a2;
1675                self.bmat[b1] = -self.bmat[bjp] - self.bmat[bjpn];
1676                let (z1, zjp, zjpn) = (self.iz(1, j), self.iz(jp, j), self.iz(jpn, j));
1677                self.zmat[z1] = 2.0f64.sqrt() / (a1 * a2).abs();
1678                self.zmat[zjp] = self.zmat[z1] * a2 * temp;
1679                self.zmat[zjpn] = -self.zmat[z1] * a1 * temp;
1680            } else {
1681                let (b1, bjp, bjn) = (self.ib(1, j), self.ib(jp, j), self.ib(j + npt, j));
1682                self.bmat[b1] = -1.0 / a1;
1683                self.bmat[bjp] = 1.0 / a1;
1684                self.bmat[bjn] = -0.5 * (a1 * a1);
1685            }
1686        }
1687
1688        if npt >= n + np {
1689            for k in (2 * np)..=npt {
1690                let iw = (((k - np) as f64 - 0.5) / n as f64) as usize;
1691                let ip = k - np - iw * n;
1692                let mut iq = ip + iw;
1693                if iq > n {
1694                    iq -= n;
1695                }
1696                let pk = self.pid(k);
1697                self.w[pk] = ip as f64 + iq as f64 / np as f64 + sfrac;
1698                let temp = 1.0 / (self.w[self.pa(1, ip)] * self.w[self.pa(1, iq)]);
1699                let idx = [
1700                    self.iz(1, k - np),
1701                    self.iz(ip + 1, k - np),
1702                    self.iz(iq + 1, k - np),
1703                    self.iz(k, k - np),
1704                ];
1705                self.zmat[idx[0]] = temp;
1706                self.zmat[idx[1]] = -temp;
1707                self.zmat[idx[2]] = -temp;
1708                self.zmat[idx[3]] = temp;
1709            }
1710        }
1711        let mut nrem = npt;
1712        let mut kold = 1;
1713        let mut knew = self.kopt;
1714        let mut beta = 0.0;
1715        let mut denom = 0.0;
1716
1717        // Labels 80 and 120, which need no values.
1718        'l80: loop {
1719            for j in 1..=n {
1720                let (a, b) = (self.ib(kold, j), self.ib(knew, j));
1721                self.bmat.swap(a, b);
1722            }
1723            for j in 1..=nptm {
1724                let (a, b) = (self.iz(kold, j), self.iz(knew, j));
1725                self.zmat.swap(a, b);
1726            }
1727            let (pk, pn) = (self.pid(kold), self.pid(knew));
1728            self.w[pk] = self.w[pn];
1729            self.w[pn] = 0.0;
1730            let rn = self.rw(ndim + knew);
1731            self.w[rn] = 0.0;
1732            nrem -= 1;
1733            if knew != self.kopt {
1734                self.vlag.swap(kold, knew);
1735                let rbase = self.rw(1);
1736                update(
1737                    n,
1738                    npt,
1739                    &mut self.bmat,
1740                    &mut self.zmat,
1741                    ndim,
1742                    &mut self.vlag[1..],
1743                    beta,
1744                    denom,
1745                    knew,
1746                    &mut self.w[rbase..],
1747                );
1748                if nrem == 0 {
1749                    return Label::L190Post;
1750                }
1751                for k in 1..=npt {
1752                    let r = self.rw(ndim + k);
1753                    self.w[r] = self.w[r].abs();
1754                }
1755            }
1756
1757            'l120: loop {
1758                let mut dsqmin = 0.0;
1759                for k in 1..=npt {
1760                    let v = self.w[self.rw(ndim + k)];
1761                    if v > 0.0 && (dsqmin == 0.0 || v < dsqmin) {
1762                        knew = k;
1763                        dsqmin = v;
1764                    }
1765                }
1766                if dsqmin == 0.0 {
1767                    break 'l80;
1768                }
1769
1770                for j in 1..=n {
1771                    let r = self.rw(npt + j);
1772                    self.w[r] = self.xpt[self.ix(knew, j)];
1773                }
1774                for k in 1..=npt {
1775                    let mut sum = 0.0;
1776                    if k == self.kopt {
1777                    } else if self.w[self.pid(k)] == 0.0 {
1778                        for j in 1..=n {
1779                            sum += self.w[self.rw(npt + j)] * self.xpt[self.ix(k, j)];
1780                        }
1781                    } else {
1782                        let (ip, iq) = self.pid_parts(k);
1783                        if ip > 0 {
1784                            sum = self.w[self.rw(npt + ip)] * self.w[self.pa(1, ip)];
1785                        }
1786                        if iq > 0 {
1787                            let iw = if ip == 0 { 2 } else { 1 };
1788                            sum += self.w[self.rw(npt + iq)] * self.w[self.pa(iw, iq)];
1789                        }
1790                    }
1791                    let r = self.rw(k);
1792                    self.w[r] = 0.5 * sum * sum;
1793                }
1794
1795                for k in 1..=npt {
1796                    let mut sum = 0.0;
1797                    for j in 1..=n {
1798                        sum += self.bmat[self.ib(k, j)] * self.w[self.rw(npt + j)];
1799                    }
1800                    self.vlag[k] = sum;
1801                }
1802                beta = 0.0;
1803                for j in 1..=nptm {
1804                    let mut sum = 0.0;
1805                    for k in 1..=npt {
1806                        sum += self.zmat[self.iz(k, j)] * self.w[self.rw(k)];
1807                    }
1808                    beta -= sum * sum;
1809                    for k in 1..=npt {
1810                        self.vlag[k] += sum * self.zmat[self.iz(k, j)];
1811                    }
1812                }
1813                let mut bsum = 0.0;
1814                let mut distsq = 0.0;
1815                for j in 1..=n {
1816                    let mut sum = 0.0;
1817                    for k in 1..=npt {
1818                        sum += self.bmat[self.ib(k, j)] * self.w[self.rw(k)];
1819                    }
1820                    let jp = j + npt;
1821                    bsum += sum * self.w[self.rw(jp)];
1822                    for ip in (npt + 1)..=ndim {
1823                        sum += self.bmat[self.ib(ip, j)] * self.w[self.rw(ip)];
1824                    }
1825                    bsum += sum * self.w[self.rw(jp)];
1826                    self.vlag[jp] = sum;
1827                    let v = self.xpt[self.ix(knew, j)];
1828                    distsq += v * v;
1829                }
1830                beta = 0.5 * distsq * distsq + beta - bsum;
1831                self.vlag[self.kopt] += 1.0;
1832
1833                denom = 0.0;
1834                let mut vlmxsq = 0.0f64;
1835                for k in 1..=npt {
1836                    if self.w[self.pid(k)] != 0.0 {
1837                        let mut hdiag = 0.0;
1838                        for j in 1..=nptm {
1839                            let z = self.zmat[self.iz(k, j)];
1840                            hdiag += z * z;
1841                        }
1842                        let den = beta * hdiag + self.vlag[k] * self.vlag[k];
1843                        if den > denom {
1844                            kold = k;
1845                            denom = den;
1846                        }
1847                    }
1848                    vlmxsq = vlmxsq.max(self.vlag[k] * self.vlag[k]);
1849                }
1850                if denom <= vlmxsq * 0.01 {
1851                    let r = self.rw(ndim + knew);
1852                    self.w[r] = -self.w[r] - winc;
1853                    continue 'l120;
1854                }
1855                continue 'l80;
1856            }
1857        }
1858
1859        self.kpt = 0;
1860        Label::RescueNext
1861    }
1862
1863    /// `rescue`, label 260: the next provisional point to evaluate.
1864    fn rescue_next(&mut self) -> Label {
1865        let n = self.n;
1866        let npt = self.npt;
1867        loop {
1868            self.kpt += 1;
1869            if self.kpt > npt {
1870                return Label::L190Post;
1871            }
1872            let kpt = self.kpt;
1873            if self.w[self.pid(kpt)] == 0.0 {
1874                continue;
1875            }
1876            if self.stop_evals() {
1877                self.rc = BobyqaStatus::MaxevalReached;
1878                return Label::L190Post;
1879            }
1880
1881            let mut ih = 0;
1882            for j in 1..=n {
1883                let rj = self.rw(j);
1884                let x = self.ix(kpt, j);
1885                self.w[rj] = self.xpt[x];
1886                self.xpt[x] = 0.0;
1887                let temp = self.pq[kpt] * self.w[rj];
1888                for i in 1..=j {
1889                    ih += 1;
1890                    self.hq[ih] += temp * self.w[self.rw(i)];
1891                }
1892            }
1893            self.pq[kpt] = 0.0;
1894            let (ip, iq) = self.pid_parts(kpt);
1895            self.rip = ip;
1896            self.riq = iq;
1897            if ip > 0 {
1898                self.xp = self.w[self.pa(1, ip)];
1899                let x = self.ix(kpt, ip);
1900                self.xpt[x] = self.xp;
1901            }
1902            if iq > 0 {
1903                self.xq = self.w[self.pa(1, iq)];
1904                if ip == 0 {
1905                    self.xq = self.w[self.pa(2, iq)];
1906                }
1907                let x = self.ix(kpt, iq);
1908                self.xpt[x] = self.xq;
1909            }
1910
1911            let mut vquad = self.fbase;
1912            let mut ihp = 0;
1913            if ip > 0 {
1914                ihp = (ip + ip * ip) / 2;
1915                vquad += self.xp * (self.gopt[ip] + 0.5 * self.xp * self.hq[ihp]);
1916            }
1917            if iq > 0 {
1918                let ihq = (iq + iq * iq) / 2;
1919                vquad += self.xq * (self.gopt[iq] + 0.5 * self.xq * self.hq[ihq]);
1920                if ip > 0 {
1921                    let iw = ihp.max(ihq) - ip.abs_diff(iq);
1922                    vquad += self.xp * self.xq * self.hq[iw];
1923                }
1924            }
1925            for k in 1..=npt {
1926                let mut temp = 0.0;
1927                if ip > 0 {
1928                    temp += self.xp * self.xpt[self.ix(k, ip)];
1929                }
1930                if iq > 0 {
1931                    temp += self.xq * self.xpt[self.ix(k, iq)];
1932                }
1933                vquad += 0.5 * self.pq[k] * temp * temp;
1934            }
1935            self.rvquad = vquad;
1936
1937            for i in 1..=n {
1938                let p = self.xpt[self.ix(kpt, i)];
1939                self.x[i] = self.xl[i].max(self.xbase[i] + p).min(self.xu[i]);
1940                if p == self.sl[i] {
1941                    self.x[i] = self.xl[i];
1942                }
1943                if p == self.su[i] {
1944                    self.x[i] = self.xu[i];
1945                }
1946            }
1947            self.request(Label::RescuePost);
1948            return Label::Wait;
1949        }
1950    }
1951
1952    /// `rescue` after the value of point `kpt`: the model update.
1953    fn rescue_post(&mut self) -> Label {
1954        let n = self.n;
1955        let npt = self.npt;
1956        let nptm = npt - n - 1;
1957        let kpt = self.kpt;
1958        let f = self.f;
1959        self.fval[kpt] = f;
1960        if f < self.fval[self.kopt] {
1961            self.kopt = kpt;
1962        }
1963        if self.stop_evals() {
1964            self.rc = BobyqaStatus::MaxevalReached;
1965            return Label::L190Post;
1966        }
1967
1968        let diff = f - self.rvquad;
1969        for i in 1..=n {
1970            self.gopt[i] += diff * self.bmat[self.ib(kpt, i)];
1971        }
1972        for k in 1..=npt {
1973            let mut sum = 0.0;
1974            for j in 1..=nptm {
1975                sum += self.zmat[self.iz(k, j)] * self.zmat[self.iz(kpt, j)];
1976            }
1977            let temp = diff * sum;
1978            if self.w[self.pid(k)] == 0.0 {
1979                self.pq[k] += temp;
1980            } else {
1981                let (ip, iq) = self.pid_parts(k);
1982                let ihq = (iq * iq + iq) / 2;
1983                if ip == 0 {
1984                    let a = self.w[self.pa(2, iq)];
1985                    self.hq[ihq] += temp * (a * a);
1986                } else {
1987                    let ihp = (ip * ip + ip) / 2;
1988                    let a = self.w[self.pa(1, ip)];
1989                    self.hq[ihp] += temp * (a * a);
1990                    if iq > 0 {
1991                        let b = self.w[self.pa(1, iq)];
1992                        self.hq[ihq] += temp * (b * b);
1993                        let iw = ihp.max(ihq) - iq.abs_diff(ip);
1994                        self.hq[iw] += temp * a * b;
1995                    }
1996                }
1997            }
1998        }
1999        let p = self.pid(kpt);
2000        self.w[p] = 0.0;
2001        Label::RescueNext
2002    }
2003}
2004
2005/////////////////
2006// Subroutines //
2007/////////////////
2008
2009/// Powell's `TRSBOX`: an approximate minimiser of the quadratic model in the
2010/// intersection of the trust region and the box.
2011///
2012/// Arguments are zero-based slices of the one-based arrays; `xpt` is the
2013/// column-major `npt * n` matrix.
2014///
2015/// ### Params
2016///
2017/// * `n`, `npt` - Dimensions
2018/// * `xpt`, `xopt`, `gopt`, `hq`, `pq`, `sl`, `su` - The model and box
2019/// * `delta` - Trust-region radius
2020/// * `xnew`, `d`, `gnew`, `xbdi`, `s`, `hs`, `hred` - Outputs and work space
2021/// * `dsq` - Set to the squared step length
2022/// * `crvmin` - Set to the least curvature seen
2023fn trsbox(
2024    n: usize,
2025    npt: usize,
2026    xpt: &[f64],
2027    xopt: &[f64],
2028    gopt: &[f64],
2029    hq: &[f64],
2030    pq: &[f64],
2031    sl: &[f64],
2032    su: &[f64],
2033    delta: f64,
2034    xnew: &mut [f64],
2035    d: &mut [f64],
2036    gnew: &mut [f64],
2037    xbdi: &mut [f64],
2038    s: &mut [f64],
2039    hs: &mut [f64],
2040    hred: &mut [f64],
2041    dsq: &mut f64,
2042    crvmin: &mut f64,
2043) {
2044    #[derive(Clone, Copy)]
2045    enum At {
2046        L20,
2047        L30,
2048        L50,
2049        L90,
2050        L100,
2051        L120,
2052        L150,
2053        L190,
2054        L210,
2055    }
2056    let ix = |k: usize, j: usize| (k - 1) + (j - 1) * npt;
2057
2058    let mut iterc: i64 = 0;
2059    let mut nact: usize = 0;
2060    for i in 1..=n {
2061        xbdi[i - 1] = 0.0;
2062        if xopt[i - 1] <= sl[i - 1] {
2063            if gopt[i - 1] >= 0.0 {
2064                xbdi[i - 1] = -1.0;
2065            }
2066        } else if xopt[i - 1] >= su[i - 1] && gopt[i - 1] <= 0.0 {
2067            xbdi[i - 1] = 1.0;
2068        }
2069        if xbdi[i - 1] != 0.0 {
2070            nact += 1;
2071        }
2072        d[i - 1] = 0.0;
2073        gnew[i - 1] = gopt[i - 1];
2074    }
2075    let mut delsq = delta * delta;
2076    let mut qred = 0.0;
2077    *crvmin = -1.0;
2078
2079    let mut beta = 0.0;
2080    let mut stepsq = 0.0;
2081    let mut gredsq = 0.0;
2082    let mut itermax: i64 = 0;
2083    let mut ggsav = 0.0;
2084    let mut iact: usize = 0;
2085    let mut dredsq = 0.0;
2086    let mut dredg = 0.0;
2087    let mut sredg = 0.0;
2088    let mut itcsav: i64 = 0;
2089    let mut angbd = 0.0;
2090    let mut xsav = 0.0;
2091    let mut shs;
2092    let mut sdec;
2093
2094    let mut at = At::L20;
2095    loop {
2096        match at {
2097            At::L20 => {
2098                beta = 0.0;
2099                at = At::L30;
2100            }
2101            At::L30 => {
2102                stepsq = 0.0;
2103                for i in 1..=n {
2104                    if xbdi[i - 1] != 0.0 {
2105                        s[i - 1] = 0.0;
2106                    } else if beta == 0.0 {
2107                        s[i - 1] = -gnew[i - 1];
2108                    } else {
2109                        s[i - 1] = beta * s[i - 1] - gnew[i - 1];
2110                    }
2111                    stepsq += s[i - 1] * s[i - 1];
2112                }
2113                if stepsq == 0.0 {
2114                    at = At::L190;
2115                    continue;
2116                }
2117                if beta == 0.0 {
2118                    gredsq = stepsq;
2119                    itermax = iterc + n as i64 - nact as i64;
2120                }
2121                if gredsq * delsq <= qred * 1e-4 * qred {
2122                    at = At::L190;
2123                    continue;
2124                }
2125                at = At::L210;
2126            }
2127            At::L50 => {
2128                let mut resid = delsq;
2129                let mut ds = 0.0;
2130                shs = 0.0;
2131                for i in 1..=n {
2132                    if xbdi[i - 1] == 0.0 {
2133                        resid -= d[i - 1] * d[i - 1];
2134                        ds += s[i - 1] * d[i - 1];
2135                        shs += s[i - 1] * hs[i - 1];
2136                    }
2137                }
2138                if resid <= 0.0 {
2139                    at = At::L90;
2140                    continue;
2141                }
2142                let temp = (stepsq * resid + ds * ds).sqrt();
2143                let blen = if ds < 0.0 {
2144                    (temp - ds) / stepsq
2145                } else {
2146                    resid / (temp + ds)
2147                };
2148                let mut stplen = blen;
2149                if shs > 0.0 {
2150                    stplen = blen.min(gredsq / shs);
2151                }
2152
2153                iact = 0;
2154                for i in 1..=n {
2155                    if s[i - 1] != 0.0 {
2156                        let xsum = xopt[i - 1] + d[i - 1];
2157                        let temp = if s[i - 1] > 0.0 {
2158                            (su[i - 1] - xsum) / s[i - 1]
2159                        } else {
2160                            (sl[i - 1] - xsum) / s[i - 1]
2161                        };
2162                        if temp < stplen {
2163                            stplen = temp;
2164                            iact = i;
2165                        }
2166                    }
2167                }
2168
2169                sdec = 0.0;
2170                if stplen > 0.0 {
2171                    iterc += 1;
2172                    let temp = shs / stepsq;
2173                    if iact == 0 && temp > 0.0 {
2174                        *crvmin = crvmin.min(temp);
2175                        if *crvmin == -1.0 {
2176                            *crvmin = temp;
2177                        }
2178                    }
2179                    ggsav = gredsq;
2180                    gredsq = 0.0;
2181                    for i in 1..=n {
2182                        gnew[i - 1] += stplen * hs[i - 1];
2183                        if xbdi[i - 1] == 0.0 {
2184                            gredsq += gnew[i - 1] * gnew[i - 1];
2185                        }
2186                        d[i - 1] += stplen * s[i - 1];
2187                    }
2188                    sdec = (stplen * (ggsav - 0.5 * stplen * shs)).max(0.0);
2189                    qred += sdec;
2190                }
2191
2192                if iact > 0 {
2193                    nact += 1;
2194                    xbdi[iact - 1] = 1.0;
2195                    if s[iact - 1] < 0.0 {
2196                        xbdi[iact - 1] = -1.0;
2197                    }
2198                    delsq -= d[iact - 1] * d[iact - 1];
2199                    if delsq <= 0.0 {
2200                        at = At::L90;
2201                        continue;
2202                    }
2203                    at = At::L20;
2204                    continue;
2205                }
2206
2207                if stplen < blen {
2208                    if iterc == itermax {
2209                        at = At::L190;
2210                        continue;
2211                    }
2212                    if sdec <= qred * 0.01 {
2213                        at = At::L190;
2214                        continue;
2215                    }
2216                    beta = gredsq / ggsav;
2217                    at = At::L30;
2218                    continue;
2219                }
2220                at = At::L90;
2221            }
2222            At::L90 => {
2223                *crvmin = 0.0;
2224                at = At::L100;
2225            }
2226            At::L100 => {
2227                if nact + 1 >= n {
2228                    at = At::L190;
2229                    continue;
2230                }
2231                dredsq = 0.0;
2232                dredg = 0.0;
2233                gredsq = 0.0;
2234                for i in 1..=n {
2235                    if xbdi[i - 1] == 0.0 {
2236                        dredsq += d[i - 1] * d[i - 1];
2237                        dredg += d[i - 1] * gnew[i - 1];
2238                        gredsq += gnew[i - 1] * gnew[i - 1];
2239                        s[i - 1] = d[i - 1];
2240                    } else {
2241                        s[i - 1] = 0.0;
2242                    }
2243                }
2244                itcsav = iterc;
2245                at = At::L210;
2246            }
2247            At::L120 => {
2248                iterc += 1;
2249                let temp = gredsq * dredsq - dredg * dredg;
2250                if temp <= qred * 1e-4 * qred {
2251                    at = At::L190;
2252                    continue;
2253                }
2254                let temp = temp.sqrt();
2255                for i in 1..=n {
2256                    if xbdi[i - 1] == 0.0 {
2257                        s[i - 1] = (dredg * d[i - 1] - dredsq * gnew[i - 1]) / temp;
2258                    } else {
2259                        s[i - 1] = 0.0;
2260                    }
2261                }
2262                sredg = -temp;
2263
2264                angbd = 1.0;
2265                iact = 0;
2266                let mut back_to_100 = false;
2267                for i in 1..=n {
2268                    if xbdi[i - 1] == 0.0 {
2269                        let tempa = xopt[i - 1] + d[i - 1] - sl[i - 1];
2270                        let tempb = su[i - 1] - xopt[i - 1] - d[i - 1];
2271                        if tempa <= 0.0 {
2272                            nact += 1;
2273                            xbdi[i - 1] = -1.0;
2274                            back_to_100 = true;
2275                            break;
2276                        } else if tempb <= 0.0 {
2277                            nact += 1;
2278                            xbdi[i - 1] = 1.0;
2279                            back_to_100 = true;
2280                            break;
2281                        }
2282                        let ssq = d[i - 1] * d[i - 1] + s[i - 1] * s[i - 1];
2283                        let a = xopt[i - 1] - sl[i - 1];
2284                        let temp = ssq - a * a;
2285                        if temp > 0.0 {
2286                            let temp = temp.sqrt() - s[i - 1];
2287                            if angbd * temp > tempa {
2288                                angbd = tempa / temp;
2289                                iact = i;
2290                                xsav = -1.0;
2291                            }
2292                        }
2293                        let b = su[i - 1] - xopt[i - 1];
2294                        let temp = ssq - b * b;
2295                        if temp > 0.0 {
2296                            let temp = temp.sqrt() + s[i - 1];
2297                            if angbd * temp > tempb {
2298                                angbd = tempb / temp;
2299                                iact = i;
2300                                xsav = 1.0;
2301                            }
2302                        }
2303                    }
2304                }
2305                if back_to_100 {
2306                    at = At::L100;
2307                    continue;
2308                }
2309                at = At::L210;
2310            }
2311            At::L150 => {
2312                shs = 0.0;
2313                let mut dhs = 0.0;
2314                let mut dhd = 0.0;
2315                for i in 1..=n {
2316                    if xbdi[i - 1] == 0.0 {
2317                        shs += s[i - 1] * hs[i - 1];
2318                        dhs += d[i - 1] * hs[i - 1];
2319                        dhd += d[i - 1] * hred[i - 1];
2320                    }
2321                }
2322
2323                let mut redmax = 0.0;
2324                let mut isav: i64 = 0;
2325                let mut redsav = 0.0;
2326                let mut rdprev = 0.0;
2327                let mut rdnext = 0.0;
2328                let iu = (angbd * 17.0 + 3.1) as i64;
2329                for i in 1..=iu {
2330                    let angt = angbd * i as f64 / iu as f64;
2331                    let sth = (angt + angt) / (1.0 + angt * angt);
2332                    let temp = shs + angt * (angt * dhd - dhs - dhs);
2333                    let rednew = sth * (angt * dredg - sredg - 0.5 * sth * temp);
2334                    if rednew > redmax {
2335                        redmax = rednew;
2336                        isav = i;
2337                        rdprev = redsav;
2338                    } else if i == isav + 1 {
2339                        rdnext = rednew;
2340                    }
2341                    redsav = rednew;
2342                }
2343
2344                if isav == 0 {
2345                    at = At::L190;
2346                    continue;
2347                }
2348                let mut angt = 0.0;
2349                if isav < iu {
2350                    let temp = (rdnext - rdprev) / (redmax + redmax - rdprev - rdnext);
2351                    angt = angbd * (isav as f64 + 0.5 * temp) / iu as f64;
2352                }
2353                let cth = (1.0 - angt * angt) / (1.0 + angt * angt);
2354                let sth = (angt + angt) / (1.0 + angt * angt);
2355                let temp = shs + angt * (angt * dhd - dhs - dhs);
2356                sdec = sth * (angt * dredg - sredg - 0.5 * sth * temp);
2357                if sdec <= 0.0 {
2358                    at = At::L190;
2359                    continue;
2360                }
2361
2362                dredg = 0.0;
2363                gredsq = 0.0;
2364                for i in 1..=n {
2365                    gnew[i - 1] = gnew[i - 1] + (cth - 1.0) * hred[i - 1] + sth * hs[i - 1];
2366                    if xbdi[i - 1] == 0.0 {
2367                        d[i - 1] = cth * d[i - 1] + sth * s[i - 1];
2368                        dredg += d[i - 1] * gnew[i - 1];
2369                        gredsq += gnew[i - 1] * gnew[i - 1];
2370                    }
2371                    hred[i - 1] = cth * hred[i - 1] + sth * hs[i - 1];
2372                }
2373                qred += sdec;
2374                if iact > 0 && isav == iu {
2375                    nact += 1;
2376                    xbdi[iact - 1] = xsav;
2377                    at = At::L100;
2378                    continue;
2379                }
2380
2381                if sdec > qred * 0.01 {
2382                    at = At::L120;
2383                    continue;
2384                }
2385                at = At::L190;
2386            }
2387            At::L190 => {
2388                *dsq = 0.0;
2389                for i in 1..=n {
2390                    xnew[i - 1] = (xopt[i - 1] + d[i - 1]).min(su[i - 1]).max(sl[i - 1]);
2391                    if xbdi[i - 1] == -1.0 {
2392                        xnew[i - 1] = sl[i - 1];
2393                    }
2394                    if xbdi[i - 1] == 1.0 {
2395                        xnew[i - 1] = su[i - 1];
2396                    }
2397                    d[i - 1] = xnew[i - 1] - xopt[i - 1];
2398                    *dsq += d[i - 1] * d[i - 1];
2399                }
2400                return;
2401            }
2402            At::L210 => {
2403                let mut ih = 0;
2404                for j in 1..=n {
2405                    hs[j - 1] = 0.0;
2406                    for i in 1..=j {
2407                        ih += 1;
2408                        if i < j {
2409                            hs[j - 1] += hq[ih - 1] * s[i - 1];
2410                        }
2411                        hs[i - 1] += hq[ih - 1] * s[j - 1];
2412                    }
2413                }
2414                for k in 1..=npt {
2415                    if pq[k - 1] != 0.0 {
2416                        let mut temp = 0.0;
2417                        for j in 1..=n {
2418                            temp += xpt[ix(k, j)] * s[j - 1];
2419                        }
2420                        temp *= pq[k - 1];
2421                        for i in 1..=n {
2422                            hs[i - 1] += temp * xpt[ix(k, i)];
2423                        }
2424                    }
2425                }
2426                if *crvmin != 0.0 {
2427                    at = At::L50;
2428                    continue;
2429                }
2430                if iterc > itcsav {
2431                    at = At::L150;
2432                    continue;
2433                }
2434                hred[..n].copy_from_slice(&hs[..n]);
2435                at = At::L120;
2436            }
2437        }
2438    }
2439}
2440
2441/// Powell's `ALTMOV`: two alternative new positions for interpolation point
2442/// `knew`, along a line through `xopt` and by a constrained Cauchy step.
2443///
2444/// ### Params
2445///
2446/// * `n`, `npt`, `ndim` - Dimensions
2447/// * `xpt`, `xopt`, `bmat`, `zmat`, `sl`, `su` - The model and box
2448/// * `kopt`, `knew` - Best point and the point to move, one-based
2449/// * `adelt` - Bound on the step
2450/// * `xnew`, `xalt` - The two candidates
2451/// * `alpha` - Set to `H(knew, knew)`
2452/// * `cauchy` - Set to the squared Lagrange value at `xalt`
2453/// * `glag`, `hcol`, `w` - Work space of `n`, `npt` and `2n`
2454fn altmov(
2455    n: usize,
2456    npt: usize,
2457    xpt: &[f64],
2458    xopt: &[f64],
2459    bmat: &[f64],
2460    zmat: &[f64],
2461    ndim: usize,
2462    sl: &[f64],
2463    su: &[f64],
2464    kopt: usize,
2465    knew: usize,
2466    adelt: f64,
2467    xnew: &mut [f64],
2468    xalt: &mut [f64],
2469    alpha: &mut f64,
2470    cauchy: &mut f64,
2471    glag: &mut [f64],
2472    hcol: &mut [f64],
2473    w: &mut [f64],
2474) {
2475    let ix = |k: usize, j: usize| (k - 1) + (j - 1) * npt;
2476    let ib = |k: usize, j: usize| (k - 1) + (j - 1) * ndim;
2477    let iz = |k: usize, j: usize| (k - 1) + (j - 1) * npt;
2478    let const_ = 1.0 + 2.0f64.sqrt();
2479
2480    for k in 1..=npt {
2481        hcol[k - 1] = 0.0;
2482    }
2483    for j in 1..=(npt - n - 1) {
2484        let temp = zmat[iz(knew, j)];
2485        for k in 1..=npt {
2486            hcol[k - 1] += temp * zmat[iz(k, j)];
2487        }
2488    }
2489    *alpha = hcol[knew - 1];
2490    let ha = 0.5 * *alpha;
2491
2492    for i in 1..=n {
2493        glag[i - 1] = bmat[ib(knew, i)];
2494    }
2495    for k in 1..=npt {
2496        let mut temp = 0.0;
2497        for j in 1..=n {
2498            temp += xpt[ix(k, j)] * xopt[j - 1];
2499        }
2500        temp *= hcol[k - 1];
2501        for i in 1..=n {
2502            glag[i - 1] += temp * xpt[ix(k, i)];
2503        }
2504    }
2505
2506    let mut presav = 0.0;
2507    let mut ksav = 0;
2508    let mut stpsav = 0.0;
2509    let mut ibdsav: i64 = 0;
2510    for k in 1..=npt {
2511        if k == kopt {
2512            continue;
2513        }
2514        let mut dderiv = 0.0;
2515        let mut distsq = 0.0;
2516        for i in 1..=n {
2517            let temp = xpt[ix(k, i)] - xopt[i - 1];
2518            dderiv += glag[i - 1] * temp;
2519            distsq += temp * temp;
2520        }
2521        let mut subd = adelt / distsq.sqrt();
2522        let mut slbd = -subd;
2523        let mut ilbd: i64 = 0;
2524        let mut iubd: i64 = 0;
2525        let sumin = 1.0f64.min(subd);
2526
2527        for i in 1..=n {
2528            let temp = xpt[ix(k, i)] - xopt[i - 1];
2529            if temp > 0.0 {
2530                if slbd * temp < sl[i - 1] - xopt[i - 1] {
2531                    slbd = (sl[i - 1] - xopt[i - 1]) / temp;
2532                    ilbd = -(i as i64);
2533                }
2534                if subd * temp > su[i - 1] - xopt[i - 1] {
2535                    subd = sumin.max((su[i - 1] - xopt[i - 1]) / temp);
2536                    iubd = i as i64;
2537                }
2538            } else if temp < 0.0 {
2539                if slbd * temp > su[i - 1] - xopt[i - 1] {
2540                    slbd = (su[i - 1] - xopt[i - 1]) / temp;
2541                    ilbd = i as i64;
2542                }
2543                if subd * temp < sl[i - 1] - xopt[i - 1] {
2544                    subd = sumin.max((sl[i - 1] - xopt[i - 1]) / temp);
2545                    iubd = -(i as i64);
2546                }
2547            }
2548        }
2549
2550        let mut step;
2551        let mut vlag;
2552        let mut isbd;
2553        if k == knew {
2554            let diff = dderiv - 1.0;
2555            step = slbd;
2556            vlag = slbd * (dderiv - slbd * diff);
2557            isbd = ilbd;
2558            let temp = subd * (dderiv - subd * diff);
2559            if temp.abs() > vlag.abs() {
2560                step = subd;
2561                vlag = temp;
2562                isbd = iubd;
2563            }
2564            let tempd = 0.5 * dderiv;
2565            let tempa = tempd - diff * slbd;
2566            let tempb = tempd - diff * subd;
2567            if tempa * tempb < 0.0 {
2568                let temp = tempd * tempd / diff;
2569                if temp.abs() > vlag.abs() {
2570                    step = tempd / diff;
2571                    vlag = temp;
2572                    isbd = 0;
2573                }
2574            }
2575        } else {
2576            step = slbd;
2577            vlag = slbd * (1.0 - slbd);
2578            isbd = ilbd;
2579            let temp = subd * (1.0 - subd);
2580            if temp.abs() > vlag.abs() {
2581                step = subd;
2582                vlag = temp;
2583                isbd = iubd;
2584            }
2585            if subd > 0.5 && vlag.abs() < 0.25 {
2586                step = 0.5;
2587                vlag = 0.25;
2588                isbd = 0;
2589            }
2590            vlag *= dderiv;
2591        }
2592
2593        let temp = step * (1.0 - step) * distsq;
2594        let predsq = vlag * vlag * (vlag * vlag + ha * temp * temp);
2595        if predsq > presav {
2596            presav = predsq;
2597            ksav = k;
2598            stpsav = step;
2599            ibdsav = isbd;
2600        }
2601    }
2602
2603    // The C leaves `ksav` at zero when no line predicts a positive
2604    // denominator and then reads outside `xpt`; `stpsav` is zero then, so any
2605    // row gives `xnew = xopt`.
2606    let ksav = ksav.max(1);
2607    for i in 1..=n {
2608        let temp = xopt[i - 1] + stpsav * (xpt[ix(ksav, i)] - xopt[i - 1]);
2609        xnew[i - 1] = sl[i - 1].max(su[i - 1].min(temp));
2610    }
2611    if ibdsav < 0 {
2612        let i = (-ibdsav) as usize;
2613        xnew[i - 1] = sl[i - 1];
2614    }
2615    if ibdsav > 0 {
2616        let i = ibdsav as usize;
2617        xnew[i - 1] = su[i - 1];
2618    }
2619
2620    let bigstp = adelt + adelt;
2621    let mut iflag = 0;
2622    let mut csave = 0.0;
2623    loop {
2624        // Label 100.
2625        let mut wfixsq = 0.0;
2626        let mut ggfree = 0.0;
2627        for i in 1..=n {
2628            w[i - 1] = 0.0;
2629            let tempa = (xopt[i - 1] - sl[i - 1]).min(glag[i - 1]);
2630            let tempb = (xopt[i - 1] - su[i - 1]).max(glag[i - 1]);
2631            if tempa > 0.0 || tempb < 0.0 {
2632                w[i - 1] = bigstp;
2633                ggfree += glag[i - 1] * glag[i - 1];
2634            }
2635        }
2636        if ggfree == 0.0 {
2637            *cauchy = 0.0;
2638            return;
2639        }
2640
2641        // Label 120.
2642        let mut step = 0.0;
2643        loop {
2644            let temp = adelt * adelt - wfixsq;
2645            if temp > 0.0 {
2646                let wsqsav = wfixsq;
2647                step = (temp / ggfree).sqrt();
2648                ggfree = 0.0;
2649                for i in 1..=n {
2650                    if w[i - 1] == bigstp {
2651                        let temp = xopt[i - 1] - step * glag[i - 1];
2652                        if temp <= sl[i - 1] {
2653                            w[i - 1] = sl[i - 1] - xopt[i - 1];
2654                            wfixsq += w[i - 1] * w[i - 1];
2655                        } else if temp >= su[i - 1] {
2656                            w[i - 1] = su[i - 1] - xopt[i - 1];
2657                            wfixsq += w[i - 1] * w[i - 1];
2658                        } else {
2659                            ggfree += glag[i - 1] * glag[i - 1];
2660                        }
2661                    }
2662                }
2663                if wfixsq > wsqsav && ggfree > 0.0 {
2664                    continue;
2665                }
2666            }
2667            break;
2668        }
2669
2670        let mut gw = 0.0;
2671        for i in 1..=n {
2672            if w[i - 1] == bigstp {
2673                w[i - 1] = -step * glag[i - 1];
2674                xalt[i - 1] = sl[i - 1].max(su[i - 1].min(xopt[i - 1] + w[i - 1]));
2675            } else if w[i - 1] == 0.0 {
2676                xalt[i - 1] = xopt[i - 1];
2677            } else if glag[i - 1] > 0.0 {
2678                xalt[i - 1] = sl[i - 1];
2679            } else {
2680                xalt[i - 1] = su[i - 1];
2681            }
2682            gw += glag[i - 1] * w[i - 1];
2683        }
2684
2685        let mut curv = 0.0;
2686        for k in 1..=npt {
2687            let mut temp = 0.0;
2688            for j in 1..=n {
2689                temp += xpt[ix(k, j)] * w[j - 1];
2690            }
2691            curv += hcol[k - 1] * temp * temp;
2692        }
2693        if iflag == 1 {
2694            curv = -curv;
2695        }
2696        if curv > -gw && curv < -const_ * gw {
2697            let scale = -gw / curv;
2698            for i in 1..=n {
2699                let temp = xopt[i - 1] + scale * w[i - 1];
2700                xalt[i - 1] = sl[i - 1].max(su[i - 1].min(temp));
2701            }
2702            let a = 0.5 * gw * scale;
2703            *cauchy = a * a;
2704        } else {
2705            let a = gw + 0.5 * curv;
2706            *cauchy = a * a;
2707        }
2708
2709        if iflag == 0 {
2710            for i in 1..=n {
2711                glag[i - 1] = -glag[i - 1];
2712                w[n + i - 1] = xalt[i - 1];
2713            }
2714            csave = *cauchy;
2715            iflag = 1;
2716            continue;
2717        }
2718        if csave > *cauchy {
2719            for i in 1..=n {
2720                xalt[i - 1] = w[n + i - 1];
2721            }
2722            *cauchy = csave;
2723        }
2724        return;
2725    }
2726}
2727
2728/// Powell's `UPDATE`: revises `bmat` and `zmat` for moving point `knew`.
2729///
2730/// ### Params
2731///
2732/// * `n`, `npt`, `ndim` - Dimensions
2733/// * `bmat`, `zmat` - Column-major matrices, updated
2734/// * `vlag` - Zero-based, length `ndim`; its `knew`-th entry is decremented
2735/// * `beta`, `denom` - Updating parameters
2736/// * `knew` - The point being moved, one-based
2737/// * `w` - Work space, zero-based, length at least `ndim`
2738fn update(
2739    n: usize,
2740    npt: usize,
2741    bmat: &mut [f64],
2742    zmat: &mut [f64],
2743    ndim: usize,
2744    vlag: &mut [f64],
2745    beta: f64,
2746    denom: f64,
2747    knew: usize,
2748    w: &mut [f64],
2749) {
2750    let ib = |k: usize, j: usize| (k - 1) + (j - 1) * ndim;
2751    let iz = |k: usize, j: usize| (k - 1) + (j - 1) * npt;
2752    let nptm = npt - n - 1;
2753
2754    let mut ztest = 0.0f64;
2755    for k in 1..=npt {
2756        for j in 1..=nptm {
2757            ztest = ztest.max(zmat[iz(k, j)].abs());
2758        }
2759    }
2760    ztest *= 1e-20;
2761
2762    for j in 2..=nptm {
2763        if zmat[iz(knew, j)].abs() > ztest {
2764            let a = zmat[iz(knew, 1)];
2765            let b = zmat[iz(knew, j)];
2766            let temp = (a * a + b * b).sqrt();
2767            let tempa = a / temp;
2768            let tempb = b / temp;
2769            for i in 1..=npt {
2770                let temp = tempa * zmat[iz(i, 1)] + tempb * zmat[iz(i, j)];
2771                zmat[iz(i, j)] = tempa * zmat[iz(i, j)] - tempb * zmat[iz(i, 1)];
2772                zmat[iz(i, 1)] = temp;
2773            }
2774        }
2775        zmat[iz(knew, j)] = 0.0;
2776    }
2777
2778    for i in 1..=npt {
2779        w[i - 1] = zmat[iz(knew, 1)] * zmat[iz(i, 1)];
2780    }
2781    let alpha = w[knew - 1];
2782    let tau = vlag[knew - 1];
2783    vlag[knew - 1] -= 1.0;
2784
2785    let temp = denom.sqrt();
2786    let tempb = zmat[iz(knew, 1)] / temp;
2787    let tempa = tau / temp;
2788    for i in 1..=npt {
2789        zmat[iz(i, 1)] = tempa * zmat[iz(i, 1)] - tempb * vlag[i - 1];
2790    }
2791
2792    for j in 1..=n {
2793        let jp = npt + j;
2794        w[jp - 1] = bmat[ib(knew, j)];
2795        let tempa = (alpha * vlag[jp - 1] - tau * w[jp - 1]) / denom;
2796        let tempb = (-beta * w[jp - 1] - tau * vlag[jp - 1]) / denom;
2797        for i in 1..=jp {
2798            bmat[ib(i, j)] = bmat[ib(i, j)] + tempa * vlag[i - 1] + tempb * w[i - 1];
2799            if i > npt {
2800                bmat[ib(jp, i - npt)] = bmat[ib(i, j)];
2801            }
2802        }
2803    }
2804}
2805
2806/// [`BobyqaStepper`] driven by a closure.
2807///
2808/// ### Params
2809///
2810/// * `f` - Objective
2811/// * `x0` - Starting point, inside the box
2812/// * `lower` - Lower bounds
2813/// * `upper` - Upper bounds
2814/// * `params` - Stopping knobs, or nloptr's defaults
2815///
2816/// ### Returns
2817///
2818/// The result, or the error of [`BobyqaStepper::new`].
2819pub fn bobyqa<F>(
2820    mut f: F,
2821    x0: &[f64],
2822    lower: &[f64],
2823    upper: &[f64],
2824    params: Option<BobyqaParams>,
2825) -> Result<BobyqaResult, EdgeErrors>
2826where
2827    F: FnMut(&[f64]) -> f64,
2828{
2829    let mut stepper = BobyqaStepper::new(x0, lower, upper, params)?;
2830    while let Some(x) = stepper.ask() {
2831        let value = f(x);
2832        stepper.tell(value);
2833    }
2834    Ok(stepper.result())
2835}
2836
2837///////////
2838// Tests //
2839///////////
2840
2841#[cfg(test)]
2842mod tests {
2843    use super::*;
2844    use std::path::PathBuf;
2845
2846    /// One of the bounded problems `tests/r/generate_fixtures.R` traces through
2847    /// `nloptr::bobyqa`.
2848    struct Case {
2849        /// Fixture suffix.
2850        tag: &'static str,
2851        /// Objective, written as R evaluates it.
2852        f: fn(&[f64]) -> f64,
2853        /// Start.
2854        x0: &'static [f64],
2855        /// Lower bounds.
2856        lo: &'static [f64],
2857        /// Upper bounds.
2858        hi: &'static [f64],
2859        /// nloptr's exit status.
2860        status: BobyqaStatus,
2861    }
2862
2863    fn rosen(x: &[f64]) -> f64 {
2864        100.0 * (x[1] - x[0] * x[0]).powi(2) + (1.0 - x[0]).powi(2)
2865    }
2866
2867    fn corner(x: &[f64]) -> f64 {
2868        (x[0] - 3.0).powi(2) + 10.0 * (x[1] + 1.0).powi(2) + x[0] * x[1]
2869    }
2870
2871    fn ripple(x: &[f64]) -> f64 {
2872        let u = 10000.0 * x[0] + 30000.0 * x[1];
2873        let t = (u - 2.0 * ((u + 1.0) / 2.0).floor()).abs();
2874        (x[0] - 0.25).powi(2) + 2.0 * (x[1] + 0.125).powi(2) + 0.5 * x[0] * x[1] + 0.5 * (t * t)
2875    }
2876
2877    fn valley(x: &[f64]) -> f64 {
2878        (x[0] + x[1] - 1.0).powi(2)
2879    }
2880
2881    fn chain3(x: &[f64]) -> f64 {
2882        (x[0] - 1.0).powi(2)
2883            + 100.0 * (x[1] - x[0] * x[0]).powi(2)
2884            + (x[2] - x[1]).powi(2)
2885            + 0.5 * x[2].powi(2).powi(2)
2886    }
2887
2888    const CASES: [Case; 6] = [
2889        Case {
2890            tag: "rosen",
2891            f: rosen,
2892            x0: &[-1.2, 1.0],
2893            lo: &[-2.0, -1.0],
2894            hi: &[2.0, 3.0],
2895            status: BobyqaStatus::XtolReached,
2896        },
2897        Case {
2898            tag: "rosen_edge",
2899            f: rosen,
2900            x0: &[0.4001, 0.2],
2901            lo: &[0.4, 0.2],
2902            hi: &[2.0, 2.0],
2903            status: BobyqaStatus::XtolReached,
2904        },
2905        Case {
2906            tag: "corner",
2907            f: corner,
2908            x0: &[1.0, 1.0],
2909            lo: &[0.0, 0.0],
2910            hi: &[2.0, 2.0],
2911            status: BobyqaStatus::XtolReached,
2912        },
2913        Case {
2914            tag: "chain3",
2915            f: chain3,
2916            x0: &[0.5, 2.0, -1.0],
2917            lo: &[-3.0, -3.0, -3.0],
2918            hi: &[3.0, 3.0, 3.0],
2919            status: BobyqaStatus::XtolReached,
2920        },
2921        // The ripple degrades the interpolation set until `rescue` runs.
2922        Case {
2923            tag: "rescue",
2924            f: ripple,
2925            x0: &[-1.0, 0.25],
2926            lo: &[-2.0, -2.0],
2927            hi: &[2.0, 2.0],
2928            status: BobyqaStatus::XtolReached,
2929        },
2930        Case {
2931            tag: "roundoff",
2932            f: valley,
2933            x0: &[0.0, 0.0],
2934            lo: &[-2.0, -2.0],
2935            hi: &[2.0, 2.0],
2936            status: BobyqaStatus::RoundoffLimited,
2937        },
2938    ];
2939
2940    /// Reads one trace: a row per evaluation, the point then the value.
2941    fn trace(tag: &str) -> Vec<Vec<f64>> {
2942        let path: PathBuf = [
2943            env!("CARGO_MANIFEST_DIR"),
2944            "tests",
2945            "data",
2946            "e2e",
2947            &format!("bobyqa_{tag}.csv"),
2948        ]
2949        .iter()
2950        .collect();
2951        std::fs::read_to_string(&path)
2952            .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()))
2953            .lines()
2954            .map(|l| {
2955                l.split(',')
2956                    .map(|v| v.trim().parse().expect("number"))
2957                    .collect()
2958            })
2959            .collect()
2960    }
2961
2962    #[test]
2963    fn test_bobyqa_retraces_nloptr_point_for_point() {
2964        for case in &CASES {
2965            let want = trace(case.tag);
2966            let mut got: Vec<Vec<f64>> = Vec::new();
2967            let res = bobyqa(
2968                |x| {
2969                    let v = (case.f)(x);
2970                    got.push(x.iter().copied().chain([v]).collect());
2971                    v
2972                },
2973                case.x0,
2974                case.lo,
2975                case.hi,
2976                None,
2977            )
2978            .expect("valid box");
2979            assert_eq!(got.len(), want.len(), "{}: evaluation count", case.tag);
2980            for (i, (g, w)) in got.iter().zip(&want).enumerate() {
2981                assert_eq!(g, w, "{}: evaluation {i}", case.tag);
2982            }
2983            assert_eq!(res.evaluations, want.len());
2984            assert_eq!(res.status, case.status, "{}: status", case.tag);
2985        }
2986    }
2987
2988    #[test]
2989    fn test_bobyqa_lands_on_an_active_bound() {
2990        let res = bobyqa(corner, &[1.0, 1.0], &[0.0, 0.0], &[2.0, 2.0], None).expect("valid box");
2991        assert_eq!(res.x, vec![2.0, 0.0]);
2992        assert_eq!(res.f, 11.0);
2993    }
2994}