#![allow(clippy::needless_range_loop)]
use crate::error::{DEError, Result};
#[derive(Debug, Clone, Copy)]
struct Lcg(u32);
impl Lcg {
fn new(seed: u32) -> Self {
Self(seed)
}
fn next_u32(&mut self) -> u32 {
self.0 = self.0.wrapping_mul(1103515245).wrapping_add(12345);
self.0
}
fn urand(&mut self, a: f64, b: f64) -> f64 {
a + (self.next_u32() as f64) * (b - a) / (u32::MAX as f64)
}
}
#[derive(Debug, Clone, Copy)]
pub struct StopCriteria {
pub stopval: f64,
pub ftol_abs: f64,
pub ftol_rel: f64,
pub xtol_abs: f64,
pub xtol_rel: f64,
pub maxeval: usize,
}
impl Default for StopCriteria {
fn default() -> Self {
Self {
stopval: f64::NEG_INFINITY,
ftol_abs: 0.0,
ftol_rel: 0.0,
xtol_abs: 0.0,
xtol_rel: 1e-4,
maxeval: 0,
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct NativeReport {
pub x: Vec<f64>,
pub fun: f64,
pub max_violation: f64,
pub feasible: bool,
pub success: bool,
pub message: &'static str,
pub nfev: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)] enum Status {
Success,
StopvalReached,
FtolReached,
XtolReached,
MaxevalReached,
RoundoffLimited,
Failure,
InvalidArgs,
ForcedStop,
}
impl Status {
fn message(self) -> &'static str {
match self {
Status::Success => "Success",
Status::StopvalReached => "StopvalReached",
Status::FtolReached => "FtolReached",
Status::XtolReached => "XtolReached",
Status::MaxevalReached => "MaxevalReached",
Status::RoundoffLimited => "RoundoffLimited",
Status::Failure => "Failure",
Status::InvalidArgs => "InvalidArgs",
Status::ForcedStop => "ForcedStop",
}
}
fn is_success(self) -> bool {
matches!(
self,
Status::Success
| Status::StopvalReached
| Status::FtolReached
| Status::XtolReached
| Status::MaxevalReached
)
}
}
#[inline(always)]
fn idx2(i: usize, j: usize, dim1: usize) -> usize {
i + j * dim1
}
#[allow(clippy::too_many_arguments)]
pub fn cobyla_native<F, G>(
n: usize,
f: F,
constraints: &[G],
bounds: &[(f64, f64)],
x: &mut [f64],
dx: &[f64],
stop: &StopCriteria,
) -> Result<NativeReport>
where
F: Fn(&[f64]) -> f64,
G: Fn(&[f64]) -> f64,
{
if x.len() != n || bounds.len() != n || dx.len() != n {
return Err(DEError::BoundsMismatch {
lower_len: bounds.len(),
upper_len: n,
});
}
for (i, (lo, hi)) in bounds.iter().enumerate() {
if lo > hi {
return Err(DEError::InvalidBounds {
index: i,
lower: *lo,
upper: *hi,
});
}
}
let mut s = vec![1.0_f64; n];
if n > 1 && dx.iter().any(|&v| v != dx[0]) {
for i in 1..n {
s[i] = dx[i] / dx[0];
}
}
for j in 0..n {
if s[j] == 0.0 || !s[j].is_finite() {
return Err(DEError::InvalidBounds {
index: j,
lower: bounds[j].0,
upper: bounds[j].1,
});
}
}
let lb_rescaled: Vec<f64> = bounds.iter().zip(&s).map(|((lo, _), si)| lo / si).collect();
let ub_rescaled: Vec<f64> = bounds.iter().zip(&s).map(|((_, hi), si)| hi / si).collect();
let (lb_rescaled, ub_rescaled): (Vec<f64>, Vec<f64>) = lb_rescaled
.iter()
.zip(ub_rescaled.iter())
.map(|(&lo, &hi)| if lo > hi { (hi, lo) } else { (lo, hi) })
.unzip();
let mut x_rescaled: Vec<f64> = x.iter().zip(&s).map(|(xi, si)| xi / si).collect();
let rhobeg = (dx[0] / s[0]).abs();
let mut rhoend = if stop.xtol_rel > 0.0 {
stop.xtol_rel * rhobeg
} else {
1e-4 * rhobeg
};
if stop.xtol_abs > 0.0 {
for j in 0..n {
let candidate = stop.xtol_abs / s[j].abs();
if rhoend < candidate {
rhoend = candidate;
}
}
}
let m = constraints.len();
let n_bound_lo = bounds.iter().filter(|b| b.0.is_finite()).count();
let n_bound_hi = bounds.iter().filter(|b| b.1.is_finite()).count();
let m_total = m + n_bound_lo + n_bound_hi;
let mpp = m_total + 2;
let mut con = vec![0.0_f64; mpp];
let mut sim = vec![0.0_f64; n * (n + 1)]; let mut simi = vec![0.0_f64; n * n];
let mut datmat = vec![0.0_f64; mpp * (n + 1)];
let mut a_mat = vec![0.0_f64; n * (m_total + 1)]; let mut vsig = vec![0.0_f64; n];
let mut veta = vec![0.0_f64; n];
let mut sigbar = vec![0.0_f64; n];
let mut dx_ws = vec![0.0_f64; n];
let mut iact = vec![0_usize; m_total + 1];
let mut z_ws = vec![0.0_f64; n * n];
let mut zdota = vec![0.0_f64; n];
let mut vmultc = vec![0.0_f64; m_total + 1];
let mut sdirn = vec![0.0_f64; n];
let mut dxnew = vec![0.0_f64; n];
let mut vmultd = vec![0.0_f64; m_total + 1];
let lb_rs_for_cfc = lb_rescaled.clone();
let ub_rs_for_cfc = ub_rescaled.clone();
let calcfc = |x_rs: &[f64], con: &mut [f64]| -> f64 {
let x_orig: Vec<f64> = x_rs.iter().zip(&s).map(|(xi, si)| xi * si).collect();
for k in 0..m {
con[k] = -constraints[k](&x_orig);
}
let mut idx = m;
for (i, &(lo, hi)) in bounds.iter().enumerate() {
if lo.is_finite() {
con[idx] = x_rs[i] - lb_rs_for_cfc[i];
idx += 1;
}
if hi.is_finite() {
con[idx] = ub_rs_for_cfc[i] - x_rs[i];
idx += 1;
}
}
f(&x_orig)
};
let mut nfev = 0usize;
let mut minf = f64::INFINITY;
let status = cobylb(
n,
m_total,
mpp,
&mut x_rescaled,
&mut minf,
rhobeg,
rhoend,
stop,
&lb_rescaled,
&ub_rescaled,
&mut con,
&mut sim,
&mut simi,
&mut datmat,
&mut a_mat,
&mut vsig,
&mut veta,
&mut sigbar,
&mut dx_ws,
&mut iact,
&mut z_ws,
&mut zdota,
&mut vmultc,
&mut sdirn,
&mut dxnew,
&mut vmultd,
&mut nfev,
&calcfc,
);
for i in 0..n {
x[i] = x_rescaled[i] * s[i];
x[i] = x[i].clamp(bounds[i].0, bounds[i].1);
}
let max_v: f64 = constraints
.iter()
.map(|g| g(x))
.fold(0.0_f64, |acc, v| acc.max(v));
Ok(NativeReport {
x: x.to_vec(),
fun: minf,
max_violation: max_v.max(0.0),
feasible: max_v <= 0.0,
success: status.is_success(),
message: status.message(),
nfev,
})
}
#[derive(Debug, Clone, Copy)]
enum BodyState {
EvalCallback, AfterInitialSimplex, IdentifyBest, SolveLp, PostTrial, RhoCheck, CleanExit, UseCurrentExit, }
#[allow(clippy::too_many_arguments)]
fn cobylb<F>(
n: usize,
m: usize,
mpp: usize,
x: &mut [f64],
minf: &mut f64,
rhobeg: f64,
rhoend: f64,
stop: &StopCriteria,
lb: &[f64],
ub: &[f64],
con: &mut [f64],
sim: &mut [f64],
simi: &mut [f64],
datmat: &mut [f64],
a_mat: &mut [f64],
vsig: &mut [f64],
veta: &mut [f64],
sigbar: &mut [f64],
dx: &mut [f64],
iact: &mut [usize],
z_ws: &mut [f64],
zdota: &mut [f64],
vmultc: &mut [f64],
sdirn: &mut [f64],
dxnew: &mut [f64],
vmultd: &mut [f64],
nfev: &mut usize,
calcfc: &F,
) -> Status
where
F: Fn(&[f64], &mut [f64]) -> f64,
{
let np = n + 1; let mp = m; let _ = mp;
let alpha = 0.25_f64;
let beta = 2.1_f64;
let gamma_ = 0.5_f64;
let delta = 1.1_f64;
let mut rho = rhobeg;
let mut parmu = 0.0_f64;
let mut seed = Lcg::new((n as u32).wrapping_add(m as u32));
*minf = f64::INFINITY;
for i in 0..n {
sim[idx2(i, np - 1, n)] = x[i];
for j in 0..n {
sim[idx2(i, j, n)] = 0.0;
simi[idx2(i, j, n)] = 0.0;
}
let lb_i = lb[i];
let ub_i = ub[i];
let mut rhocur = if (ub_i - lb_i) <= f64::EPSILON {
rho
} else {
let mut r = rho;
if x[i] + r > ub_i {
if x[i] - r >= lb_i {
r = -r;
} else if ub_i - x[i] > x[i] - lb_i {
r = 0.5 * (ub_i - x[i]);
} else {
r = 0.5 * (x[i] - lb_i);
}
}
r
};
if rhocur == 0.0 {
rhocur = f64::EPSILON;
}
sim[idx2(i, i, n)] = rhocur;
simi[idx2(i, i, n)] = 1.0 / rhocur;
}
let mut jdrop: usize = n;
let mut ibrnch = 0i32;
let mut iflag = 0i32;
let mut ifull = 0i32;
let mut nbest;
let mut f = 0.0_f64;
let mut resmax = 0.0_f64;
let mut prerec = 0.0_f64;
let mut prerem = 0.0_f64;
let mut sum = 0.0_f64;
let mut pending_exit: Option<Status> = None;
let mut state = BodyState::EvalCallback;
loop {
match state {
BodyState::EvalCallback => {
if stop.maxeval > 0 && *nfev >= stop.maxeval {
pending_exit = Some(Status::MaxevalReached);
state = BodyState::CleanExit;
continue;
}
*nfev += 1;
f = calcfc(x, &mut con[..m]);
resmax = 0.0;
let mut feasible = true;
for k in 0..m {
let v = -con[k];
if v > resmax {
resmax = v;
}
if v > 0.0 {
feasible = false;
}
}
if f < stop.stopval && feasible {
*minf = f;
state = BodyState::UseCurrentExit;
continue;
}
con[m] = f; con[mpp - 1] = resmax;
if ibrnch == 1 {
state = BodyState::PostTrial;
continue;
}
for k in 0..mpp {
datmat[idx2(k, jdrop, mpp)] = con[k];
}
if *nfev > np {
state = BodyState::AfterInitialSimplex;
continue;
}
if jdrop < n {
if datmat[idx2(m, np - 1, mpp)] <= f {
x[jdrop] = sim[idx2(jdrop, np - 1, n)];
} else {
let rhocur = x[jdrop] - sim[idx2(jdrop, np - 1, n)];
sim[idx2(jdrop, np - 1, n)] = x[jdrop];
for k in 0..mpp {
datmat[idx2(k, jdrop, mpp)] = datmat[idx2(k, np - 1, mpp)];
datmat[idx2(k, np - 1, mpp)] = con[k];
}
for k in 0..=jdrop {
sim[idx2(jdrop, k, n)] = -rhocur;
let mut temp = 0.0;
for ii in k..=jdrop {
temp -= simi[idx2(ii, k, n)];
}
simi[idx2(jdrop, k, n)] = temp;
}
}
}
if *nfev <= n {
jdrop = *nfev - 1;
x[jdrop] += sim[idx2(jdrop, jdrop, n)];
state = BodyState::EvalCallback;
continue;
}
state = BodyState::AfterInitialSimplex;
}
BodyState::AfterInitialSimplex => {
ibrnch = 1;
state = BodyState::IdentifyBest;
}
BodyState::IdentifyBest => {
let mut phimin =
datmat[idx2(m, np - 1, mpp)] + parmu * datmat[idx2(mpp - 1, np - 1, mpp)];
nbest = np - 1;
for j in 0..n {
let temp = datmat[idx2(m, j, mpp)] + parmu * datmat[idx2(mpp - 1, j, mpp)];
if temp < phimin {
nbest = j;
phimin = temp;
} else if temp == phimin
&& parmu == 0.0
&& datmat[idx2(mpp - 1, j, mpp)] < datmat[idx2(mpp - 1, nbest, mpp)]
{
nbest = j;
}
}
if nbest < n {
for ii in 0..mpp {
let temp = datmat[idx2(ii, np - 1, mpp)];
datmat[idx2(ii, np - 1, mpp)] = datmat[idx2(ii, nbest, mpp)];
datmat[idx2(ii, nbest, mpp)] = temp;
}
for ii in 0..n {
let temp = sim[idx2(ii, nbest, n)];
sim[idx2(ii, nbest, n)] = 0.0;
sim[idx2(ii, np - 1, n)] += temp;
let mut tempa = 0.0;
for k in 0..n {
sim[idx2(ii, k, n)] -= temp;
tempa -= simi[idx2(k, ii, n)];
}
simi[idx2(nbest, ii, n)] = tempa;
}
}
let mut error = 0.0_f64;
for ii in 0..n {
for j in 0..n {
let mut temp = if ii == j { -1.0 } else { 0.0 };
for k in 0..n {
if sim[idx2(k, j, n)] != 0.0 {
temp += simi[idx2(ii, k, n)] * sim[idx2(k, j, n)];
}
}
error = error.max(temp.abs());
}
}
if error > 0.1 {
break Status::RoundoffLimited;
}
for k in 0..=m {
con[k] = -datmat[idx2(k, np - 1, mpp)];
let mut w = vec![0.0_f64; n];
for j in 0..n {
w[j] = datmat[idx2(k, j, mpp)] + con[k];
}
for ii in 0..n {
let mut temp = 0.0;
for j in 0..n {
temp += w[j] * simi[idx2(j, ii, n)];
}
if k == m {
temp = -temp;
}
a_mat[idx2(ii, k, n)] = temp;
}
}
iflag = 1;
let parsig = alpha * rho;
let pareta = beta * rho;
for j in 0..n {
let mut wsig = 0.0;
let mut weta = 0.0;
for ii in 0..n {
let s = simi[idx2(j, ii, n)];
wsig += s * s;
let e = sim[idx2(ii, j, n)];
weta += e * e;
}
vsig[j] = 1.0 / wsig.sqrt();
veta[j] = weta.sqrt();
if vsig[j] < parsig || veta[j] > pareta {
iflag = 0;
}
}
if ibrnch == 1 || iflag == 1 {
state = BodyState::SolveLp;
continue;
}
let mut local_jdrop: Option<usize> = None;
let mut temp = pareta;
for j in 0..n {
if veta[j] > temp {
local_jdrop = Some(j);
temp = veta[j];
}
}
if local_jdrop.is_none() {
for j in 0..n {
if vsig[j] < temp {
local_jdrop = Some(j);
temp = vsig[j];
}
}
}
let jd = local_jdrop.expect("at least one vertex must be droppable");
jdrop = jd;
let temp_step = gamma_ * rho * vsig[jd];
for ii in 0..n {
dx[ii] = temp_step * simi[idx2(jd, ii, n)];
}
let mut cvmaxp = 0.0_f64;
let mut cvmaxm = 0.0_f64;
for k in 0..=m {
let mut s = 0.0;
for ii in 0..n {
s += a_mat[idx2(ii, k, n)] * dx[ii];
}
sum = s; if k < m {
let temp = datmat[idx2(k, np - 1, mpp)];
cvmaxp = cvmaxp.max(-s - temp);
cvmaxm = cvmaxm.max(s - temp);
}
}
let mut dxsign = 1.0_f64;
if parmu * (cvmaxp - cvmaxm) > sum + sum {
dxsign = -1.0;
}
let mut temp = 0.0;
for ii in 0..n {
dx[ii] = dxsign * dx[ii] * seed.urand(0.01, 1.0);
let xi = sim[idx2(ii, np - 1, n)];
loop {
if xi + dx[ii] > ub[ii] {
dx[ii] = -dx[ii];
}
if xi + dx[ii] >= lb[ii] {
break;
}
if xi - dx[ii] <= ub[ii] {
dx[ii] = -dx[ii];
break;
}
dx[ii] *= 0.5;
}
sim[idx2(ii, jd, n)] = dx[ii];
temp += simi[idx2(jd, ii, n)] * dx[ii];
}
for ii in 0..n {
simi[idx2(jd, ii, n)] /= temp;
}
for j in 0..n {
if j != jd {
let mut t = 0.0;
for ii in 0..n {
t += simi[idx2(j, ii, n)] * dx[ii];
}
for ii in 0..n {
simi[idx2(j, ii, n)] -= t * simi[idx2(jd, ii, n)];
}
}
x[j] = sim[idx2(j, np - 1, n)] + dx[j];
}
state = BodyState::EvalCallback;
}
BodyState::SolveLp => {
let lp_status = trstlp(
n, m, a_mat, con, rho, dx, &mut ifull, iact, z_ws, zdota, vmultc, sdirn, dxnew,
vmultd,
);
if lp_status != Status::Success {
break lp_status;
}
for ii in 0..n {
let xi = sim[idx2(ii, np - 1, n)];
if xi + dx[ii] > ub[ii] {
dx[ii] = ub[ii] - xi;
}
if xi + dx[ii] < lb[ii] {
dx[ii] = lb[ii] - xi;
}
}
if ifull == 0 {
let mut t = 0.0;
for ii in 0..n {
t += dx[ii] * dx[ii];
}
if t < rho * 0.25 * rho {
ibrnch = 1;
state = BodyState::RhoCheck;
continue;
}
}
let mut resnew = 0.0_f64;
con[m] = 0.0;
let mut last_sum = 0.0_f64;
for k in 0..=m {
let mut s = con[k];
for ii in 0..n {
s -= a_mat[idx2(ii, k, n)] * dx[ii];
}
if k < m {
resnew = resnew.max(s);
}
last_sum = s;
}
sum = last_sum;
let mut barmu = 0.0_f64;
prerec = datmat[idx2(mpp - 1, np - 1, mpp)] - resnew;
if prerec > 0.0 {
barmu = sum / prerec;
}
if parmu < barmu * 1.5 {
parmu = barmu * 2.0;
let phi =
datmat[idx2(m, np - 1, mpp)] + parmu * datmat[idx2(mpp - 1, np - 1, mpp)];
let mut restart = false;
for j in 0..n {
let temp = datmat[idx2(m, j, mpp)] + parmu * datmat[idx2(mpp - 1, j, mpp)];
if temp < phi {
restart = true;
break;
}
if temp == phi
&& parmu == 0.0
&& datmat[idx2(mpp - 1, j, mpp)] < datmat[idx2(mpp - 1, np - 1, mpp)]
{
restart = true;
break;
}
}
if restart {
state = BodyState::IdentifyBest;
continue;
}
}
prerem = parmu * prerec - sum;
for ii in 0..n {
x[ii] = sim[idx2(ii, np - 1, n)] + dx[ii];
}
ibrnch = 1;
state = BodyState::EvalCallback;
}
BodyState::PostTrial => {
let vmold =
datmat[idx2(m, np - 1, mpp)] + parmu * datmat[idx2(mpp - 1, np - 1, mpp)];
let vmnew = f + parmu * resmax;
let mut trured = vmold - vmnew;
if parmu == 0.0 && f == datmat[idx2(m, np - 1, mpp)] {
prerem = prerec;
trured = datmat[idx2(mpp - 1, np - 1, mpp)] - resmax;
}
let mut ratio = if trured <= 0.0 { 1.0 } else { 0.0 };
let mut local_jdrop: usize = usize::MAX;
for j in 0..n {
let mut temp = 0.0_f64;
for ii in 0..n {
temp += simi[idx2(j, ii, n)] * dx[ii];
}
let temp = temp.abs();
if temp > ratio {
local_jdrop = j;
ratio = temp;
}
sigbar[j] = temp * vsig[j];
}
let edgmax_init = delta * rho;
let mut edgmax = edgmax_init;
let mut l: Option<usize> = None;
let parsig = alpha * rho;
for j in 0..n {
if sigbar[j] >= parsig || sigbar[j] >= vsig[j] {
let temp = if trured > 0.0 {
let mut t = 0.0;
for ii in 0..n {
let d = dx[ii] - sim[idx2(ii, j, n)];
t += d * d;
}
t.sqrt()
} else {
veta[j]
};
if temp > edgmax {
l = Some(j);
edgmax = temp;
}
}
}
if let Some(ll) = l {
local_jdrop = ll;
}
if local_jdrop == usize::MAX {
state = BodyState::RhoCheck;
continue;
}
jdrop = local_jdrop;
let mut temp = 0.0_f64;
for ii in 0..n {
sim[idx2(ii, jdrop, n)] = dx[ii];
temp += simi[idx2(jdrop, ii, n)] * dx[ii];
}
for ii in 0..n {
simi[idx2(jdrop, ii, n)] /= temp;
}
for j in 0..n {
if j != jdrop {
let mut t = 0.0;
for ii in 0..n {
t += simi[idx2(j, ii, n)] * dx[ii];
}
for ii in 0..n {
simi[idx2(j, ii, n)] -= t * simi[idx2(jdrop, ii, n)];
}
}
}
for k in 0..mpp {
datmat[idx2(k, jdrop, mpp)] = con[k];
}
if trured > 0.0 && trured >= prerem * 0.1 {
if trured >= prerem * 0.9 && trured <= prerem * 1.1 && iflag != 0 {
rho *= 2.0;
}
state = BodyState::IdentifyBest;
continue;
}
state = BodyState::RhoCheck;
}
BodyState::RhoCheck => {
if iflag == 0 {
ibrnch = 0;
state = BodyState::IdentifyBest;
continue;
}
let fbest = if ifull == 1 {
f
} else {
datmat[idx2(m, np - 1, mpp)]
};
if fbest < *minf
&& stop.ftol_rel > 0.0
&& relstop(fbest, *minf, stop.ftol_rel, stop.ftol_abs)
{
*minf = fbest;
break Status::FtolReached;
}
*minf = fbest;
if rho > rhoend {
rho *= 0.5;
if rho <= rhoend * 1.5 {
rho = rhoend;
}
if parmu > 0.0 {
let mut denom = 0.0_f64;
let mut cmin = 0.0_f64;
let mut cmax = 0.0_f64;
for k in 0..=m {
cmin = datmat[idx2(k, np - 1, mpp)];
cmax = cmin;
for ii in 0..n {
let v = datmat[idx2(k, ii, mpp)];
cmin = cmin.min(v);
cmax = cmax.max(v);
}
if k < m && cmin < cmax * 0.5 {
let temp = cmax.max(0.0) - cmin;
if denom <= 0.0 {
denom = temp;
} else {
denom = denom.min(temp);
}
}
}
if denom == 0.0 {
parmu = 0.0;
} else if cmax - cmin < parmu * denom {
parmu = (cmax - cmin) / denom;
}
}
state = BodyState::IdentifyBest;
continue;
}
let exit = if rhoend > 0.0 {
Status::XtolReached
} else {
Status::RoundoffLimited
};
if ifull == 1 {
*minf = f;
break exit;
}
state = BodyState::CleanExit;
}
BodyState::CleanExit => {
for ii in 0..n {
x[ii] = sim[idx2(ii, np - 1, n)];
}
f = datmat[idx2(m, np - 1, mpp)];
resmax = datmat[idx2(mpp - 1, np - 1, mpp)];
let _ = resmax; *minf = f;
break pending_exit.unwrap_or(Status::Success);
}
BodyState::UseCurrentExit => {
*minf = f;
break Status::StopvalReached;
}
}
}
}
fn relstop(new: f64, old: f64, tol_rel: f64, tol_abs: f64) -> bool {
if !new.is_finite() || !old.is_finite() {
return false;
}
let diff = (new - old).abs();
diff < tol_abs || diff < 0.5 * tol_rel * (new.abs() + old.abs())
}
#[allow(clippy::too_many_arguments)]
fn trstlp(
n: usize,
m: usize,
a_mat: &[f64],
b: &mut [f64],
rho: f64,
dx: &mut [f64],
ifull: &mut i32,
iact: &mut [usize],
z_ws: &mut [f64],
zdota: &mut [f64],
vmultc: &mut [f64],
sdirn: &mut [f64],
dxnew: &mut [f64],
vmultd: &mut [f64],
) -> Status {
*ifull = 1;
let mut mcon = m;
let mut nact: usize = 0;
let mut resmax = 0.0_f64;
let mut icon: usize = 0;
for i in 0..n {
for j in 0..n {
z_ws[idx2(i, j, n)] = 0.0;
}
z_ws[idx2(i, i, n)] = 1.0;
dx[i] = 0.0;
}
if m >= 1 {
for k in 0..m {
if b[k] > resmax {
resmax = b[k];
icon = k;
}
}
for k in 0..m {
iact[k] = k;
vmultc[k] = resmax - b[k];
}
}
let mut skip_stage1 = false;
if resmax == 0.0 {
mcon = m + 1;
icon = mcon - 1;
iact[icon] = icon;
vmultc[icon] = 0.0;
skip_stage1 = true;
}
for i in 0..n {
sdirn[i] = 0.0;
}
let _ = skip_stage1;
let mut optold: f64 = 0.0;
let mut nactx: usize = 0;
let mut icount: i32 = 0;
let mut state = TrLoopState::OuterStart;
loop {
match state {
TrLoopState::OuterStart => {
optold = 0.0;
icount = 0;
state = TrLoopState::Cycle;
}
TrLoopState::Cycle => {
let optnew = if mcon == m {
resmax
} else {
let mut s = 0.0_f64;
for i in 0..n {
s -= dx[i] * a_mat[idx2(i, mcon - 1, n)];
}
s
};
if icount == 0 || optnew < optold {
optold = optnew;
nactx = nact;
icount = 3;
} else if nact > nactx {
nactx = nact;
icount = 3;
} else {
icount -= 1;
if icount == 0 {
state = TrLoopState::L490Dispatch;
continue;
}
}
if icon < nact {
state = TrLoopState::DeleteFromActive;
continue;
}
if nact >= n {
state = TrLoopState::L490Dispatch;
continue;
}
let kk = iact[icon];
for i in 0..n {
dxnew[i] = a_mat[idx2(i, kk, n)];
}
let mut tot = 0.0_f64;
let mut k = n;
while k > nact {
let kix = k - 1;
let mut sp = 0.0_f64;
let mut spabs = 0.0_f64;
for i in 0..n {
let temp = z_ws[idx2(i, kix, n)] * dxnew[i];
sp += temp;
spabs += temp.abs();
}
let acca = spabs + sp.abs() * 0.1;
let accb = spabs + sp.abs() * 0.2;
if spabs >= acca || acca >= accb {
sp = 0.0;
}
if tot == 0.0 {
tot = sp;
} else {
let kp = kix + 1;
let temp = (sp * sp + tot * tot).sqrt();
let alpha = sp / temp;
let beta = tot / temp;
tot = temp;
for i in 0..n {
let t = alpha * z_ws[idx2(i, kix, n)] + beta * z_ws[idx2(i, kp, n)];
z_ws[idx2(i, kp, n)] =
alpha * z_ws[idx2(i, kp, n)] - beta * z_ws[idx2(i, kix, n)];
z_ws[idx2(i, kix, n)] = t;
}
}
k -= 1;
}
if tot != 0.0 {
nact += 1;
zdota[nact - 1] = tot;
vmultc[icon] = vmultc[nact - 1];
vmultc[nact - 1] = 0.0;
state = TrLoopState::AfterAdd;
continue;
}
let mut ratio = -1.0_f64;
let mut k_iter: i32 = nact as i32;
let _iout: Option<usize> = None;
while k_iter > 0 {
let kix = (k_iter - 1) as usize;
let mut zdotv = 0.0_f64;
let mut zdvabs = 0.0_f64;
for i in 0..n {
let temp = z_ws[idx2(i, kix, n)] * dxnew[i];
zdotv += temp;
zdvabs += temp.abs();
}
let acca = zdvabs + zdotv.abs() * 0.1;
let accb = zdvabs + zdotv.abs() * 0.2;
if zdvabs < acca && acca < accb {
let temp = zdotv / zdota[kix];
if temp > 0.0 && iact[kix] < m {
let tempa = vmultc[kix] / temp;
if ratio < 0.0 || tempa < ratio {
ratio = tempa;
}
}
if k_iter >= 2 {
let kw = iact[kix];
for i in 0..n {
dxnew[i] -= temp * a_mat[idx2(i, kw, n)];
}
}
vmultd[kix] = temp;
} else {
vmultd[kix] = 0.0;
}
k_iter -= 1;
}
if ratio < 0.0 {
state = TrLoopState::L490Dispatch;
continue;
}
for kk in 0..nact {
vmultc[kk] = (vmultc[kk] - ratio * vmultd[kk]).max(0.0);
}
if icon < nact {
let isave = iact[icon];
let vsave = vmultc[icon];
let mut k = icon;
while k < nact - 1 {
let kp = k + 1;
let kw = iact[kp];
let mut sp = 0.0_f64;
for i in 0..n {
sp += z_ws[idx2(i, k, n)] * a_mat[idx2(i, kw, n)];
}
let temp = (sp * sp + zdota[kp] * zdota[kp]).sqrt();
let alpha = zdota[kp] / temp;
let beta = sp / temp;
zdota[kp] = alpha * zdota[k];
zdota[k] = temp;
for i in 0..n {
let t = alpha * z_ws[idx2(i, kp, n)] + beta * z_ws[idx2(i, k, n)];
z_ws[idx2(i, kp, n)] =
alpha * z_ws[idx2(i, k, n)] - beta * z_ws[idx2(i, kp, n)];
z_ws[idx2(i, k, n)] = t;
}
iact[k] = kw;
vmultc[k] = vmultc[kp];
k = kp;
}
iact[k] = isave;
vmultc[k] = vsave;
}
let kk_new = iact[icon];
let mut temp = 0.0_f64;
for i in 0..n {
temp += z_ws[idx2(i, nact, n)] * a_mat[idx2(i, kk_new, n)];
}
if temp == 0.0 {
state = TrLoopState::L490Dispatch;
continue;
}
nact += 1;
zdota[nact - 1] = temp;
vmultc[icon] = 0.0;
vmultc[nact - 1] = ratio;
state = TrLoopState::AfterAdd;
}
TrLoopState::AfterAdd => {
let kk = iact[icon];
iact.swap(icon, nact - 1);
if mcon > m && kk != mcon - 1 {
let k = nact - 2;
let mut sp = 0.0_f64;
for i in 0..n {
sp += z_ws[idx2(i, k, n)] * a_mat[idx2(i, kk, n)];
}
let temp = (sp * sp + zdota[nact - 1] * zdota[nact - 1]).sqrt();
let alpha = zdota[nact - 1] / temp;
let beta = sp / temp;
zdota[nact - 1] = alpha * zdota[k];
zdota[k] = temp;
for i in 0..n {
let t = alpha * z_ws[idx2(i, nact - 1, n)] + beta * z_ws[idx2(i, k, n)];
z_ws[idx2(i, nact - 1, n)] =
alpha * z_ws[idx2(i, k, n)] - beta * z_ws[idx2(i, nact - 1, n)];
z_ws[idx2(i, k, n)] = t;
}
iact[nact - 1] = iact[k];
iact[k] = kk;
vmultc.swap(k, nact - 1);
}
state = TrLoopState::SetSdirn;
}
TrLoopState::DeleteFromActive => {
if icon < nact - 1 {
let isave = iact[icon];
let vsave = vmultc[icon];
let mut k = icon;
while k < nact - 1 {
let kp = k + 1;
let kk = iact[kp];
let mut sp = 0.0_f64;
for i in 0..n {
sp += z_ws[idx2(i, k, n)] * a_mat[idx2(i, kk, n)];
}
let temp = (sp * sp + zdota[kp] * zdota[kp]).sqrt();
let alpha = zdota[kp] / temp;
let beta = sp / temp;
zdota[kp] = alpha * zdota[k];
zdota[k] = temp;
for i in 0..n {
let t = alpha * z_ws[idx2(i, kp, n)] + beta * z_ws[idx2(i, k, n)];
z_ws[idx2(i, kp, n)] =
alpha * z_ws[idx2(i, k, n)] - beta * z_ws[idx2(i, kp, n)];
z_ws[idx2(i, k, n)] = t;
}
iact[k] = kk;
vmultc[k] = vmultc[kp];
k = kp;
}
iact[k] = isave;
vmultc[k] = vsave;
}
nact -= 1;
state = TrLoopState::SetSdirn;
}
TrLoopState::SetSdirn => {
if mcon > m {
let inv = 1.0 / zdota[nact - 1];
for i in 0..n {
sdirn[i] = inv * z_ws[idx2(i, nact - 1, n)];
}
} else {
let kk = iact[nact - 1];
let mut temp = 0.0_f64;
for i in 0..n {
temp += sdirn[i] * a_mat[idx2(i, kk, n)];
}
temp = (temp - 1.0) / zdota[nact - 1];
for i in 0..n {
sdirn[i] -= temp * z_ws[idx2(i, nact - 1, n)];
}
}
state = TrLoopState::ComputeStep;
}
TrLoopState::ComputeStep => {
let mut dd = rho * rho;
let mut sd = 0.0_f64;
let mut ss = 0.0_f64;
for i in 0..n {
if dx[i].abs() >= rho * 1e-6 {
dd -= dx[i] * dx[i];
}
sd += dx[i] * sdirn[i];
ss += sdirn[i] * sdirn[i];
}
if dd <= 0.0 {
state = TrLoopState::L490Dispatch;
continue;
}
let mut t = (ss * dd).sqrt();
if sd.abs() >= t * 1e-6 {
t = (ss * dd + sd * sd).sqrt();
}
let stpful = dd / (t + sd);
let mut step = stpful;
if mcon == m {
let acca = step + resmax * 0.1;
let accb = step + resmax * 0.2;
if step >= acca || acca >= accb {
state = TrLoopState::L490Dispatch;
continue;
}
step = step.min(resmax);
}
if !step.is_finite() {
return Status::RoundoffLimited;
}
for i in 0..n {
dxnew[i] = dx[i] + step * sdirn[i];
}
let mut resold = 0.0_f64;
let mut new_resmax = resmax;
if mcon == m {
resold = resmax;
new_resmax = 0.0;
for k in 0..nact {
let kk = iact[k];
let mut tt = b[kk];
for i in 0..n {
tt -= a_mat[idx2(i, kk, n)] * dxnew[i];
}
new_resmax = new_resmax.max(tt);
}
}
resmax = new_resmax;
let mut k_iter: i32 = nact as i32;
while k_iter > 0 {
let kix = (k_iter - 1) as usize;
let mut zdotw = 0.0_f64;
let mut zdwabs = 0.0_f64;
for i in 0..n {
let tt = z_ws[idx2(i, kix, n)] * dxnew[i];
zdotw += tt;
zdwabs += tt.abs();
}
let acca = zdwabs + zdotw.abs() * 0.1;
let accb = zdwabs + zdotw.abs() * 0.2;
if zdwabs >= acca || acca >= accb {
zdotw = 0.0;
}
vmultd[kix] = zdotw / zdota[kix];
if k_iter >= 2 {
let kk = iact[kix];
for i in 0..n {
dxnew[i] -= vmultd[kix] * a_mat[idx2(i, kk, n)];
}
}
k_iter -= 1;
}
if mcon > m {
vmultd[nact - 1] = vmultd[nact - 1].max(0.0);
}
for i in 0..n {
dxnew[i] = dx[i] + step * sdirn[i];
}
if mcon > nact {
for k in nact..mcon {
let kk = iact[k];
let mut s = resmax - b[kk];
let mut sumabs = resmax + b[kk].abs();
for i in 0..n {
let tt = a_mat[idx2(i, kk, n)] * dxnew[i];
s += tt;
sumabs += tt.abs();
}
let acca = sumabs + s.abs() * 0.1;
let accb = sumabs + s.abs() * 0.2;
if sumabs >= acca || acca >= accb {
s = 0.0;
}
vmultd[k] = s;
}
}
let mut ratio = 1.0_f64;
let mut new_icon: Option<usize> = None;
for k in 0..mcon {
if vmultd[k] < 0.0 {
let temp = vmultc[k] / (vmultc[k] - vmultd[k]);
if temp < ratio {
ratio = temp;
new_icon = Some(k);
}
}
}
let temp = 1.0 - ratio;
for i in 0..n {
dx[i] = temp * dx[i] + ratio * dxnew[i];
}
for k in 0..mcon {
vmultc[k] = (temp * vmultc[k] + ratio * vmultd[k]).max(0.0);
}
if mcon == m {
resmax = resold + ratio * (resmax - resold);
}
if let Some(ic) = new_icon {
icon = ic;
state = TrLoopState::Cycle;
continue;
}
if step == stpful {
return Status::Success;
}
state = TrLoopState::L490Dispatch;
}
TrLoopState::L490Dispatch => {
if mcon == m {
mcon = m + 1;
icon = mcon - 1;
iact[icon] = icon;
vmultc[icon] = 0.0;
state = TrLoopState::OuterStart;
} else {
*ifull = 0;
return Status::Success;
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
enum TrLoopState {
OuterStart, Cycle, AfterAdd, DeleteFromActive, SetSdirn, ComputeStep, L490Dispatch,
}
#[cfg(test)]
mod tests {
use super::*;
type TestConstraint = Box<dyn Fn(&[f64]) -> f64>;
#[test]
#[ignore = "manual probe; run with --ignored to print convergence details"]
fn print_convergence() {
let f = |x: &[f64]| (x[0] + 1.0).powi(2) + x[1].powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let mut x = vec![1.0, 1.0];
let report = cobyla_native(
2,
f,
&cons,
&[(-10.0, 10.0); 2],
&mut x,
&[0.5, 0.5],
&StopCriteria {
stopval: f64::NEG_INFINITY,
ftol_rel: 1e-12,
maxeval: 1000,
..Default::default()
},
)
.unwrap();
eprintln!(
"unconstrained: x={:?} fun={:.3e} status={} nfev={}",
report.x, report.fun, report.message, report.nfev
);
let f2 = |x: &[f64]| 10.0 * (x[0] + 1.0).powi(2) + x[1].powi(2);
let g0: TestConstraint = Box::new(|x: &[f64]| -x[0]);
let cons2 = vec![g0];
let mut x = vec![1.0, 1.0];
let report = cobyla_native(
2,
f2,
&cons2,
&[(-10.0, 10.0); 2],
&mut x,
&[0.5, 0.5],
&StopCriteria {
stopval: f64::NEG_INFINITY,
ftol_rel: 1e-12,
maxeval: 1000,
..Default::default()
},
)
.unwrap();
eprintln!(
"constrained: x={:?} fun={:.3e} status={} nfev={}",
report.x, report.fun, report.message, report.nfev
);
}
#[test]
fn paraboloid_unconstrained() {
let f = |x: &[f64]| (x[0] + 1.0).powi(2) + x[1].powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(-10.0, 10.0), (-10.0, 10.0)];
let mut x = vec![1.0, 1.0];
let dx = vec![0.5, 0.5];
let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
ftol_rel: 1e-10,
maxeval: 500,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
assert!(
report.fun < 1e-3,
"fun = {} should converge near 0 (status: {})",
report.fun,
report.message
);
}
#[test]
fn bug1b_isotropic_bounds_with_active_bound() {
let f = |x: &[f64]| (x[0] - 5.0).powi(2) + (x[1] - 100.0).powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(-1.0, 1.0), (-1000.0, 1000.0)];
let mut x = vec![0.0, 0.0];
let dx = vec![1.0, 1.0]; let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-8,
maxeval: 5000,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
eprintln!(
"bug1b (isotropic): x = {:?}, fun = {}, nfev = {}, status = {}",
report.x, report.fun, report.nfev, report.message
);
assert!(
(report.x[0] - 1.0).abs() < 0.01,
"x0 = {} should be near 1.0",
report.x[0]
);
assert!((report.fun - 16.0).abs() < 0.5, "fun = {}", report.fun);
}
#[test]
fn bug1_anisotropic_bounds_with_active_bound() {
let f = |x: &[f64]| (x[0] - 5.0).powi(2) + (x[1] - 100.0).powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(-1.0, 1.0), (-1000.0, 1000.0)];
let mut x = vec![0.0, 0.0];
let dx = vec![1.0, 1000.0];
let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-10,
maxeval: 2000,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
eprintln!(
"bug1: x = {:?}, fun = {}, nfev = {}, status = {}, max_v = {}",
report.x, report.fun, report.nfev, report.message, report.max_violation
);
assert!(
(report.x[0] - 1.0).abs() < 0.01,
"x0 = {} should be near 1.0 (active upper bound)",
report.x[0]
);
assert!(
(report.x[1] - 100.0).abs() < 1.0,
"x1 = {} should be near 100.0",
report.x[1]
);
assert!(
(report.fun - 16.0).abs() < 0.5,
"fun = {} should be near 16",
report.fun
);
}
#[test]
fn bug_a_fixed_dimension_does_not_explode() {
let f = |x: &[f64]| (x[0] - 0.5).powi(2) + (x[1] + 0.3).powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(0.5, 0.5), (-1.0, 1.0)];
let mut x = vec![0.5, 0.0];
let dx = vec![1e-6, 0.05];
let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-8,
maxeval: 500,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
eprintln!(
"bug_a: x = {:?}, fun = {:.3e}, nfev = {}, status = {}",
report.x, report.fun, report.nfev, report.message
);
assert!(
report.fun.is_finite(),
"fun = {} should be finite",
report.fun
);
assert!(
(report.x[0] - 0.5).abs() < 1e-6,
"x0 must stay fixed at 0.5"
);
assert!(
report.fun < 1e-3,
"fun = {} should converge near 0",
report.fun
);
}
#[test]
fn fixed_dimension_uses_consistent_rho_scale() {
let f = |x: &[f64]| (x[0] - 0.5).powi(2) + (x[1] + 0.3).powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(0.5, 0.5), (-1.0, 1.0)];
let mut x = vec![0.5, 0.0];
let dx = vec![0.5, 0.5]; let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-4,
maxeval: 500,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
assert!(
report.fun < 0.01,
"fixed-dim rho scale should improve accuracy, got fun={}",
report.fun
);
assert!(
(report.x[0] - 0.5).abs() < 1e-6,
"x0 must stay fixed at 0.5"
);
}
#[test]
fn bug_b_constrained_optimum_on_lower_bound() {
let f = |x: &[f64]| (x[0] + 5.0).powi(2) + x[1].powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(-1.0, 1.0), (-1.0, 1.0)];
let mut x = vec![0.5, 0.5];
let dx = vec![0.5, 0.5];
let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-10,
maxeval: 1000,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
eprintln!(
"bug_b: x = {:?}, fun = {:.3e}, nfev = {}, status = {}",
report.x, report.fun, report.nfev, report.message
);
assert!(
(report.x[0] - (-1.0)).abs() < 0.01,
"x0 = {} should be near -1.0 (active lower bound)",
report.x[0]
);
assert!(
report.x[1].abs() < 0.01,
"x1 = {} should be near 0",
report.x[1]
);
assert!(
(report.fun - 16.0).abs() < 0.5,
"fun = {} should be near 16",
report.fun
);
}
#[test]
fn bug2_xtol_rel_controls_rhoend() {
let f = |x: &[f64]| (x[0] + 1.0).powi(2) + x[1].powi(2);
let cons: Vec<TestConstraint> = Vec::new();
let bounds = vec![(-10.0, 10.0); 2];
let mut x_loose = vec![1.0, 1.0];
let report_loose = cobyla_native(
2,
f,
&cons,
&bounds,
&mut x_loose,
&[0.5, 0.5],
&StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-2, maxeval: 5000,
..Default::default()
},
)
.unwrap();
let mut x_tight = vec![1.0, 1.0];
let report_tight = cobyla_native(
2,
f,
&cons,
&bounds,
&mut x_tight,
&[0.5, 0.5],
&StopCriteria {
stopval: f64::NEG_INFINITY,
xtol_rel: 1e-12, maxeval: 5000,
..Default::default()
},
)
.unwrap();
eprintln!(
"bug2: loose nfev={} fun={:.3e}, tight nfev={} fun={:.3e}",
report_loose.nfev, report_loose.fun, report_tight.nfev, report_tight.fun
);
assert!(
report_tight.nfev > report_loose.nfev,
"tight xtol_rel should use more evals: tight={}, loose={}",
report_tight.nfev,
report_loose.nfev
);
assert!(
report_tight.fun <= report_loose.fun,
"tight should converge at least as well: tight={}, loose={}",
report_tight.fun,
report_loose.fun
);
}
#[test]
fn paraboloid_with_inequality() {
let f = |x: &[f64]| 10.0 * (x[0] + 1.0).powi(2) + x[1].powi(2);
let g0: TestConstraint = Box::new(|x: &[f64]| -x[0]);
let cons = vec![g0];
let bounds = vec![(-10.0, 10.0), (-10.0, 10.0)];
let mut x = vec![1.0, 1.0];
let dx = vec![0.5, 0.5];
let stop = StopCriteria {
stopval: f64::NEG_INFINITY,
ftol_rel: 1e-10,
maxeval: 1000,
..Default::default()
};
let report =
cobyla_native(2, f, &cons, &bounds, &mut x, &dx, &stop).expect("cobyla failed");
assert!(
report.x[0] >= -1e-3,
"x0 = {} should respect x0 >= 0",
report.x[0]
);
assert!(
report.fun < 11.0 && report.fun >= 0.0,
"fun = {} should be near 10",
report.fun
);
}
}