use crate::core::math::DenseMatrix;
use super::model::QuadraticModel;
pub(crate) fn build_w_dense(model: &QuadraticModel<f64>) -> DenseMatrix<f64> {
let n = model.n();
let m = model.m();
let dim = m + n + 1;
let mut w = DenseMatrix::from_fn(dim, dim, |_, _| 0.0);
for i in 0..m {
let xi = model.xpt_row(i);
for j in 0..m {
let xj = model.xpt_row(j);
let d: f64 = xi.iter().zip(xj).map(|(a, b)| a * b).sum();
w.set(i, j, 0.5 * d * d);
}
}
for i in 0..m {
let xi = model.xpt_row(i);
w.set(i, m, 1.0);
w.set(m, i, 1.0);
for k in 0..n {
w.set(i, m + 1 + k, xi[k]);
w.set(m + 1 + k, i, xi[k]);
}
}
w
}
pub(crate) fn invert_dense(a: &DenseMatrix<f64>) -> Option<DenseMatrix<f64>> {
let n = a.nrows();
assert_eq!(n, a.ncols(), "invert_dense: matrix must be square");
let w = 2 * n;
let mut aug = vec![0.0f64; n * w];
for i in 0..n {
for j in 0..n {
aug[i * w + j] = a.get(i, j);
}
aug[i * w + n + i] = 1.0;
}
for col in 0..n {
let mut piv = col;
let mut best = aug[col * w + col].abs();
for r in (col + 1)..n {
let v = aug[r * w + col].abs();
if v > best {
best = v;
piv = r;
}
}
if best < 1e-14 {
return None;
}
if piv != col {
for j in 0..w {
aug.swap(col * w + j, piv * w + j);
}
}
let pivval = aug[col * w + col];
for j in 0..w {
aug[col * w + j] /= pivval;
}
for r in 0..n {
if r == col {
continue;
}
let factor = aug[r * w + col];
if factor != 0.0 {
for j in 0..w {
aug[r * w + j] -= factor * aug[col * w + j];
}
}
}
}
let mut inv = DenseMatrix::from_fn(n, n, |_, _| 0.0);
for i in 0..n {
for j in 0..n {
inv.set(i, j, aug[i * w + n + j]);
}
}
Some(inv)
}
pub(crate) fn omega_from_factorization(model: &QuadraticModel<f64>) -> DenseMatrix<f64> {
let m = model.m();
let n = model.n();
let rank = m - n - 1;
let mut omega = DenseMatrix::from_fn(m, m, |_, _| 0.0);
for k in 0..rank {
let s = model.zsign[k];
for i in 0..m {
let zi = model.zmat.get(i, k);
for j in 0..m {
let zj = model.zmat.get(j, k);
omega.set(i, j, omega.get(i, j) + s * zi * zj);
}
}
}
omega
}
pub(crate) fn assert_h_matches_inverse(model: &QuadraticModel<f64>, tol: f64) {
let n = model.n();
let m = model.m();
let w = build_w_dense(model);
let h = invert_dense(&w).expect("KKT matrix W must be nonsingular");
let check = |got: f64, want: f64, label: &str| {
let thresh = tol * (1.0 + want.abs());
assert!(
(got - want).abs() <= thresh,
"{label}: stored {got} vs inv(W) {want} (Δ={:e}, thresh={thresh:e})",
(got - want).abs()
);
};
let omega = omega_from_factorization(model);
for i in 0..m {
for j in 0..m {
check(omega.get(i, j), h.get(i, j), &format!("Ω[{i},{j}]"));
}
}
for r in 0..n {
for j in 0..m {
check(
model.bmat_xi.get(r, j),
h.get(m + 1 + r, j),
&format!("Ξ[{r},{j}]"),
);
}
}
for r in 0..n {
for s in 0..n {
check(
model.bmat_ups.get(r, s),
h.get(m + 1 + r, m + 1 + s),
&format!("Υ[{r},{s}]"),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invert_dense_round_trips() {
use crate::core::math::LinearSolveSpd;
let a = DenseMatrix::from_row_slice(3, 3, &[4.0, 1.0, 0.5, 1.0, 3.0, 0.25, 0.5, 0.25, 2.0]);
let inv = invert_dense(&a).unwrap();
for i in 0..3 {
for j in 0..3 {
let mut acc = 0.0;
for k in 0..3 {
acc += a.get(i, k) * inv.get(k, j);
}
let want = if i == j { 1.0 } else { 0.0 };
assert!((acc - want).abs() < 1e-12, "A·inv[{i},{j}] = {acc}");
}
}
for j in 0..3 {
let mut e = vec![0.0; 3];
e[j] = 1.0;
let x = a.solve_spd(&e).unwrap();
for i in 0..3 {
assert!((x[i] - inv.get(i, j)).abs() < 1e-12);
}
}
}
#[test]
fn invert_dense_reports_singular() {
let a = DenseMatrix::from_row_slice(2, 2, &[1.0, 2.0, 2.0, 4.0]);
assert!(invert_dense(&a).is_none());
}
}