use crate::core::math::Scalar;
use crate::solver::powell::QuadraticModel;
pub(crate) struct LinearSetup<F = f64> {
pub(crate) model: QuadraticModel<F>,
pub(crate) amat: Vec<F>,
pub(crate) bvec: Vec<F>,
}
pub(crate) fn fold_constraints<F: Scalar>(
n: usize,
x0: &[F],
lower: Option<&[F]>,
upper: Option<&[F]>,
eq: &[(Vec<F>, F)],
ineq: &[(Vec<F>, F)],
) -> (Vec<F>, Vec<F>) {
let zero = F::zero();
let one = F::one();
let mut raw: Vec<(Vec<F>, F)> = Vec::new();
if let Some(lo) = lower {
for i in 0..n {
if lo[i].is_finite() {
let mut a = vec![zero; n];
a[i] = -one; raw.push((a, -lo[i]));
}
}
}
if let Some(up) = upper {
for i in 0..n {
if up[i].is_finite() {
let mut a = vec![zero; n];
a[i] = one; raw.push((a, up[i]));
}
}
}
for (a, b) in eq {
raw.push((a.iter().map(|v| -*v).collect(), -*b));
raw.push((a.clone(), *b));
}
for (a, b) in ineq {
raw.push((a.clone(), *b));
}
let mut amat = Vec::with_capacity(n * raw.len());
let mut bvec = Vec::with_capacity(raw.len());
for (a, b) in &raw {
let norm = a.iter().fold(zero, |s, v| s + *v * *v).sqrt();
if norm <= zero {
continue; }
let ax0 = (0..n).fold(zero, |s, i| s + a[i] * x0[i]);
let b_relaxed = (*b).max(ax0);
for i in 0..n {
amat.push(a[i] / norm);
}
bvec.push(b_relaxed / norm);
}
(amat, bvec)
}
fn col_dot<F: Scalar>(v: &[F], amat: &[F], j: usize, n: usize) -> F {
(0..n).fold(F::zero(), |acc, r| acc + v[r] * amat[r + j * n])
}
fn feasible_kopt<F: Scalar>(model: &QuadraticModel<F>, amat: &[F], bvec: &[F], n: usize) -> usize {
let zero = F::zero();
let npt = model.m();
let m = bvec.len();
let cval: Vec<F> = (0..npt)
.map(|k| {
let xk = model.xpt_row(k);
let mut cv = zero;
for j in 0..m {
let viol = col_dot(xk, amat, j, n) - bvec[j];
if viol > cv {
cv = viol;
}
}
cv
})
.collect();
let mut feasible: Vec<bool> = cval.iter().map(|&c| c <= zero).collect();
let mut kmin = 0;
for k in 1..npt {
if cval[k] < cval[kmin] {
kmin = k;
}
}
feasible[kmin] = true;
let mut kopt = kmin;
let mut found = false;
for k in 0..npt {
if feasible[k] && (!found || model.fval(k) < model.fval(kopt)) {
kopt = k;
found = true;
}
}
kopt
}
impl<F: Scalar> QuadraticModel<F> {
pub(crate) fn try_initialize_linear<E>(
x0: Vec<F>,
amat: Vec<F>,
bvec_abs: Vec<F>,
rho_beg: F,
npt: usize,
eval: &mut impl FnMut(&[F]) -> Result<F, E>,
) -> Result<LinearSetup<F>, E> {
let n = x0.len();
let m = bvec_abs.len();
let mut model = Self::try_initialize(x0.clone(), rho_beg, npt, eval)?;
let bvec: Vec<F> = (0..m)
.map(|j| bvec_abs[j] - col_dot(&x0, &amat, j, n))
.collect();
model.kopt = feasible_kopt(&model, &amat, &bvec, n);
Ok(LinearSetup { model, amat, bvec })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_inequality_is_normalized() {
let (amat, bvec) =
fold_constraints::<f64>(2, &[0.0, 0.0], None, None, &[], &[(vec![1.0, 1.0], 2.0)]);
let s = 1.0 / 2.0_f64.sqrt();
assert!((amat[0] - s).abs() < 1e-12);
assert!((amat[1] - s).abs() < 1e-12);
assert!((bvec[0] - 2.0 * s).abs() < 1e-12);
}
#[test]
fn box_bounds_fold_to_axis_normals() {
let (amat, bvec) = fold_constraints::<f64>(
2,
&[0.0, 0.0],
Some(&[-1.0, -3.0]),
Some(&[2.0, 4.0]),
&[],
&[],
);
assert_eq!(bvec.len(), 4);
assert_eq!(&amat[0..2], &[-1.0, 0.0]);
assert_eq!(bvec[0], 1.0);
assert_eq!(&amat[2..4], &[0.0, -1.0]);
assert_eq!(bvec[1], 3.0);
assert_eq!(&amat[4..6], &[1.0, 0.0]);
assert_eq!(bvec[2], 2.0);
assert_eq!(&amat[6..8], &[0.0, 1.0]);
assert_eq!(bvec[3], 4.0);
}
#[test]
fn equality_folds_to_two_inequalities() {
let (amat, bvec) =
fold_constraints::<f64>(2, &[0.5, 0.5], None, None, &[(vec![1.0, 1.0], 1.0)], &[]);
assert_eq!(bvec.len(), 2);
let s = 1.0 / 2.0_f64.sqrt();
assert!((amat[0] + s).abs() < 1e-12 && (amat[1] + s).abs() < 1e-12);
assert!((bvec[0] + s).abs() < 1e-12); assert!((amat[2] - s).abs() < 1e-12 && (amat[3] - s).abs() < 1e-12);
assert!((bvec[1] - s).abs() < 1e-12); }
#[test]
fn infeasible_start_relaxes_b() {
let (_amat, bvec) =
fold_constraints::<f64>(2, &[3.0, 3.0], None, None, &[], &[(vec![1.0, 1.0], 2.0)]);
let s = 1.0 / 2.0_f64.sqrt();
assert!((bvec[0] - 6.0 * s).abs() < 1e-12);
}
#[test]
fn init_shifts_b_and_picks_feasible_kopt() {
let c = [2.0, 2.0];
let f = |x: &[f64]| (x[0] - c[0]).powi(2) + (x[1] - c[1]).powi(2);
let x0 = vec![0.0, 0.0];
let (amat, bvec_abs) =
fold_constraints::<f64>(2, &x0, None, None, &[], &[(vec![1.0, 1.0], 2.0)]);
let setup = QuadraticModel::try_initialize_linear::<core::convert::Infallible>(
x0.clone(),
amat,
bvec_abs.clone(),
0.3,
5,
&mut |x| Ok(f(x)),
)
.expect("infallible");
assert!((setup.bvec[0] - bvec_abs[0]).abs() < 1e-12);
let kopt = setup.model.kopt();
let xk = setup.model.xpt_row(kopt);
let ax = super::col_dot(xk, &setup.amat, 0, 2);
assert!(ax <= setup.bvec[0] + 1e-12, "kopt point is infeasible");
}
}