use super::helpers::{apply_fixed_variable, skip_step};
use super::state::{QpPostsolveStep, QpPresolveResult, Workspace};
use crate::qp::QpProblem;
use crate::tolerances::ZERO_TOL;
pub(super) fn step7_free_var(
prob: &QpProblem,
ws: &mut Workspace,
deadline: Option<std::time::Instant>,
) -> Result<(), QpPresolveResult> {
if skip_step(7) {
return Ok(());
}
let n = prob.num_vars;
let m = prob.num_constraints;
for j in 0..n {
if deadline.is_some_and(|d| std::time::Instant::now() >= d) {
return Ok(());
}
if ws.removed_cols[j] {
continue;
}
let (lb, ub) = ws.bounds[j];
if lb != f64::NEG_INFINITY || ub != f64::INFINITY {
continue;
}
let q_nnz_j = {
let start = prob.q.col_ptr[j];
let end = prob.q.col_ptr[j + 1];
(start..end).filter(|&k| prob.q.values[k].abs() > ZERO_TOL).count()
};
if q_nnz_j > 0 {
continue;
}
let singleton_eq_rows: Vec<usize> = (0..m)
.filter(|&i| {
if ws.removed_rows[i] {
return false;
}
if prob.constraint_types[i] != crate::problem::ConstraintType::Eq {
return false;
}
let active: Vec<_> = ws.row_entries[i]
.iter()
.filter(|&&(jj, v)| !ws.removed_cols[jj] && v.abs() > ZERO_TOL)
.collect();
active.len() == 1 && active[0].0 == j
})
.collect();
if singleton_eq_rows.is_empty() {
continue;
}
let i = singleton_eq_rows[0];
let a_ij = ws.row_entries[i]
.iter()
.find(|&&(jj, _)| jj == j)
.map(|&(_, v)| v)
.unwrap_or(0.0);
if a_ij.abs() < ZERO_TOL {
continue;
}
let val = ws.b[i] / a_ij;
apply_fixed_variable(j, val, prob, ws);
ws.removed_cols[j] = true;
ws.removed_rows[i] = true;
ws.postsolve_stack.push(QpPostsolveStep::SingletonRow { row: i, col: j, val });
}
Ok(())
}