use crate::basis::{BasisManager, LuBasis};
use crate::options::{SolverOptions, WarmStartBasis};
use crate::problem::{ConstraintType, LpProblem, SolveStatus, SolverResult};
use crate::presolve::RuizScaler;
use crate::sparse::{CscMatrix, SparseVec};
use crate::tolerances::*;
use std::sync::atomic::Ordering;
#[cfg(test)]
pub(crate) static PIVOT_CLEAN_EARLY_EXIT_COUNT: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
#[cfg(test)]
pub(crate) static PIVOT_CLEAN_CLEANUP_RAN_COUNT: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
#[cfg(test)]
pub(crate) static OBJ_PROGRESS_RESET_COUNT: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
use super::dual_common::{basic_obj, compute_dual_vars_into, compute_reduced_costs_into};
use super::pricing::{PricingStrategy, SteepestEdgePricing};
use super::{StandardForm, SimplexOutcome, extract_dual_info};
const SLACK_DIAG_TOL: f64 = 1e-14;
fn extract_farkas_certificate(
a_ext: &CscMatrix,
b: &[f64],
basis: &[usize],
m: usize,
n_original: usize,
options: &SolverOptions,
) -> Vec<f64> {
if !basis.iter().any(|&col| col >= n_original) {
return vec![];
}
let mut basis_mgr = match LuBasis::new(a_ext, basis, options.max_etas) {
Ok(bm) => bm,
Err(_) => return vec![],
};
let mut y: Vec<f64> = (0..m)
.map(|i| if basis[i] >= n_original { 1.0 } else { 0.0 })
.collect();
basis_mgr.btran_dense(&mut y);
let b_norm = b.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()));
let tol = options.dual_tol * (1.0_f64).max(b_norm);
let by: f64 = b.iter().zip(y.iter()).map(|(&bi, &yi)| bi * yi).sum();
if by <= tol {
return vec![];
}
for j in 0..n_original {
let Ok((rows, vals)) = a_ext.get_column(j) else { return vec![]; };
let aty: f64 = rows.iter().zip(vals.iter()).map(|(&r, &v)| v * y[r]).sum();
if aty > tol {
return vec![];
}
}
y
}
fn extract_timeout_solution_reconciled(
sf: &StandardForm,
a: &CscMatrix,
b: &[f64],
c: &[f64],
basis: &[usize],
x_b: &[f64],
col_scale: &[f64],
max_etas: usize,
deadline: Option<std::time::Instant>,
) -> Vec<f64> {
let mut x_b_reconciled = x_b.to_vec();
let mut y = vec![0.0_f64; basis.len()];
if reconcile_final_basis_state(a, b, c, basis, &mut x_b_reconciled, &mut y, max_etas, deadline).is_ok() {
extract_solution(sf, basis, &x_b_reconciled, col_scale)
} else {
extract_solution(sf, basis, x_b, col_scale)
}
}
pub(crate) fn two_phase_simplex(sf: &StandardForm, problem: &LpProblem, options: &SolverOptions) -> SolverResult {
let m = sf.m;
let mut total_iters: usize = 0;
let (a, b, c, row_scale, col_scale) = RuizScaler::scale(&sf.a, &sf.b, &sf.c);
if sf.num_artificial == 0 {
let mut basis = sf.initial_basis.clone();
let mut x_b = b.clone();
for i in 0..m {
let col = basis[i];
if let Ok((rows, vals)) = a.get_column(col) {
for (k, &row) in rows.iter().enumerate() {
if row == i && vals[k].abs() > SLACK_DIAG_TOL {
x_b[i] /= vals[k];
break;
}
}
}
}
let mut pricing = SteepestEdgePricing::new(sf.n_total);
match revised_simplex_core(&a, &mut x_b, &c, &b, &mut basis, m, sf.n_total, sf.n_total, &mut pricing, options, &mut total_iters, false)
{
SimplexOutcome::Optimal(obj, mut y) => {
match reconcile_final_basis_state(&a, &b, &c, &basis, &mut x_b, &mut y, options.max_etas, options.deadline) {
Ok(()) => {}
Err(crate::error::SolverError::DeadlineExceeded) => {
let solution = extract_timeout_solution_reconciled(
sf,
&a,
&b,
&c,
&basis,
&x_b,
&col_scale,
options.max_etas,
options.deadline,
);
return SolverResult { status: SolveStatus::Timeout, objective: obj + sf.obj_offset, solution, iterations: total_iters, ..Default::default() };
}
Err(_) => return SolverResult::numerical_error(),
}
let solution = extract_solution(sf, &basis, &x_b, &col_scale);
if !check_eq_feasibility(problem, &solution) {
return SolverResult {
status: SolveStatus::NumericalError,
objective: obj + sf.obj_offset,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
..Default::default()
};
}
let (dual_solution, reduced_costs, slack) =
extract_dual_info(sf, problem, &y, &solution, &row_scale);
let ws = WarmStartBasis { basis: basis.clone(), x_b: x_b.clone() };
SolverResult {
status: SolveStatus::Optimal,
objective: obj + sf.obj_offset,
solution,
dual_solution,
reduced_costs,
slack,
warm_start_basis: Some(ws),
iterations: total_iters,
..Default::default()
}
}
SimplexOutcome::Unbounded => SolverResult {
status: SolveStatus::Unbounded,
objective: f64::NEG_INFINITY,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
},
SimplexOutcome::Timeout(obj) => {
let solution = extract_timeout_solution_reconciled(
sf,
&a,
&b,
&c,
&basis,
&x_b,
&col_scale,
options.max_etas,
options.deadline,
);
SolverResult {
status: SolveStatus::Timeout,
objective: obj + sf.obj_offset,
solution,
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
}
}
SimplexOutcome::SingularBasis => {
SolverResult::numerical_error()
}
}
} else {
let mut trip_rows: Vec<usize> = Vec::new();
let mut trip_cols: Vec<usize> = Vec::new();
let mut trip_vals: Vec<f64> = Vec::new();
for j in 0..a.ncols {
if let Ok((r, v)) = a.get_column(j) {
for (k, &row) in r.iter().enumerate() {
trip_rows.push(row);
trip_cols.push(j);
trip_vals.push(v[k]);
}
}
}
let mut basis = sf.initial_basis.clone();
let mut x_b = b.clone();
let mut art_col = sf.n_total;
for i in 0..m {
if !sf.needs_artificial[i] { continue; }
trip_rows.push(i);
trip_cols.push(art_col);
trip_vals.push(1.0);
basis[i] = art_col;
art_col += 1;
}
let n_ext = art_col;
let a_ext =
CscMatrix::from_triplets(&trip_rows, &trip_cols, &trip_vals, m, n_ext).unwrap();
let mut c_phase1 = vec![0.0; n_ext];
c_phase1[sf.n_total..].fill(1.0);
let crashed = if options.warm_start.is_none()
&& options.use_lp_crash_basis
&& sf.num_artificial > 0
{
try_apply_crash(&a_ext, m, sf.n_shifted, sf.n_total, &b, options.max_etas, &basis)
} else {
None
};
if let Some((crash_basis, crash_x_b)) = crashed {
basis = crash_basis;
x_b = crash_x_b;
} else {
for i in 0..m {
if let Ok((rows, vals)) = a_ext.get_column(basis[i]) {
for (k, &row) in rows.iter().enumerate() {
if row == i && vals[k].abs() > SLACK_DIAG_TOL {
x_b[i] /= vals[k];
break;
}
}
}
}
}
for i in 0..m {
if basis[i] >= sf.n_total && x_b[i].abs() <= PIVOT_TOL {
x_b[i] = PIVOT_TOL * (i as f64 + 1.0);
}
}
let mut pricing1 = SteepestEdgePricing::new(n_ext);
let phase1_outcome = revised_simplex_core(
&a_ext,
&mut x_b,
&c_phase1,
&b,
&mut basis,
m,
n_ext,
n_ext,
&mut pricing1,
options,
&mut total_iters,
true,
);
match phase1_outcome {
SimplexOutcome::Optimal(_obj, _) => {
use crate::options::MAX_PHASE1_RETRIES;
let mut phase1_feasible = false;
'retry: for attempt in 0..=MAX_PHASE1_RETRIES {
if options.deadline.is_some_and(|d| std::time::Instant::now() >= d) {
break 'retry;
}
let mut y_dummy = vec![0.0f64; m];
let rec_obj = match reconcile_final_basis_state(
&a_ext, &b, &c_phase1, &basis, &mut x_b, &mut y_dummy,
options.max_etas, options.deadline,
) {
Ok(()) => {
(0..m).map(|i| c_phase1[basis[i]] * x_b[i].max(0.0)).sum::<f64>()
}
Err(_) => break 'retry,
};
if rec_obj <= PIVOT_TOL { phase1_feasible = true; break 'retry; }
if attempt == MAX_PHASE1_RETRIES { break 'retry; }
for v in x_b.iter_mut() {
if *v < 0.0 { *v = 0.0; }
}
let mut pricing_retry = SteepestEdgePricing::new(n_ext);
match revised_simplex_core(
&a_ext, &mut x_b, &c_phase1, &b, &mut basis,
m, n_ext, n_ext, &mut pricing_retry, options,
&mut total_iters, true,
) {
SimplexOutcome::Optimal(_, _) => {}
SimplexOutcome::Unbounded => break 'retry,
SimplexOutcome::Timeout(obj1) => {
return SolverResult {
status: SolveStatus::Timeout,
objective: obj1 + sf.obj_offset,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
};
}
SimplexOutcome::SingularBasis => {
return SolverResult::numerical_error();
}
}
}
if !phase1_feasible {
let farkas = extract_farkas_certificate(
&a_ext, &b, &basis, m, sf.n_total, options,
);
return SolverResult {
status: SolveStatus::Infeasible,
objective: 0.0,
solution: vec![],
dual_solution: farkas,
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
};
}
pivot_out_degenerate_artificials(&a_ext, &mut basis, &x_b, sf, options);
let mut c_phase2 = vec![0.0; n_ext];
c_phase2[..sf.n_total].copy_from_slice(&c[..sf.n_total]);
{
let mut y_transition = vec![0.0f64; m];
match reconcile_final_basis_state(
&a_ext, &b, &c_phase2, &basis, &mut x_b, &mut y_transition,
options.max_etas, options.deadline,
) {
Ok(()) => {}
Err(crate::error::SolverError::DeadlineExceeded) => {
let solution = extract_timeout_solution_reconciled(
sf, &a_ext, &b, &c_phase2, &basis, &x_b, &col_scale,
options.max_etas, options.deadline,
);
return SolverResult { status: SolveStatus::Timeout, objective: sf.obj_offset, solution, iterations: total_iters, ..Default::default() };
}
Err(_) => return SolverResult::numerical_error(),
}
}
for i in 0..m {
if x_b[i].abs() < PIVOT_TOL {
x_b[i] = PIVOT_TOL * (i as f64 + 1.0);
}
}
for v in x_b.iter_mut() {
if *v < 0.0 { *v = 0.0; }
}
let mut pricing2 = SteepestEdgePricing::new(n_ext);
match revised_simplex_core(
&a_ext,
&mut x_b,
&c_phase2,
&b,
&mut basis,
m,
n_ext,
sf.n_total,
&mut pricing2,
options,
&mut total_iters,
false,
) {
SimplexOutcome::Optimal(obj2, mut y) => {
match reconcile_final_basis_state(&a_ext, &b, &c_phase2, &basis, &mut x_b, &mut y, options.max_etas, options.deadline) {
Ok(()) => {}
Err(crate::error::SolverError::DeadlineExceeded) => {
let solution = extract_timeout_solution_reconciled(
sf,
&a_ext,
&b,
&c_phase2,
&basis,
&x_b,
&col_scale,
options.max_etas,
options.deadline,
);
return SolverResult { status: SolveStatus::Timeout, objective: obj2 + sf.obj_offset, solution, iterations: total_iters, ..Default::default() };
}
Err(_) => return SolverResult::numerical_error(),
}
let solution = extract_solution(sf, &basis, &x_b, &col_scale);
if !check_eq_feasibility(problem, &solution) {
return SolverResult {
status: SolveStatus::NumericalError,
objective: obj2 + sf.obj_offset,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
..Default::default()
};
}
let (dual_solution, reduced_costs, slack) =
extract_dual_info(sf, problem, &y, &solution, &row_scale);
let ws = WarmStartBasis { basis: basis.clone(), x_b: x_b.clone() };
SolverResult {
status: SolveStatus::Optimal,
objective: obj2 + sf.obj_offset,
solution,
dual_solution,
reduced_costs,
slack,
warm_start_basis: Some(ws),
iterations: total_iters,
..Default::default()
}
}
SimplexOutcome::Unbounded => SolverResult {
status: SolveStatus::Unbounded,
objective: f64::NEG_INFINITY,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
},
SimplexOutcome::Timeout(obj2) => {
let solution = extract_timeout_solution_reconciled(
sf,
&a_ext,
&b,
&c_phase2,
&basis,
&x_b,
&col_scale,
options.max_etas,
options.deadline,
);
SolverResult {
status: SolveStatus::Timeout,
objective: obj2 + sf.obj_offset,
solution,
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
}
}
SimplexOutcome::SingularBasis => {
SolverResult::numerical_error()
}
}
}
SimplexOutcome::Unbounded => {
let farkas = extract_farkas_certificate(
&a_ext, &b, &basis, m, sf.n_total, options,
);
SolverResult {
status: SolveStatus::Infeasible,
objective: 0.0,
solution: vec![],
dual_solution: farkas,
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
}
},
SimplexOutcome::Timeout(obj1) => {
if obj1 <= PIVOT_TOL {
{
let mut y_dummy = vec![0.0_f64; m];
if reconcile_final_basis_state(
&a_ext,
&b,
&c_phase1,
&basis,
&mut x_b,
&mut y_dummy,
options.max_etas,
options.deadline,
)
.is_err()
{
return SolverResult {
status: SolveStatus::Timeout,
objective: obj1 + sf.obj_offset,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
};
}
}
let rec_obj: f64 = (0..m)
.map(|i| c_phase1[basis[i]] * x_b[i].max(0.0))
.sum();
if rec_obj > PIVOT_TOL {
return SolverResult {
status: SolveStatus::Timeout,
objective: obj1 + sf.obj_offset,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
};
}
pivot_out_degenerate_artificials(&a_ext, &mut basis, &x_b, sf, options);
let mut c_phase2 = vec![0.0; n_ext];
c_phase2[..sf.n_total].copy_from_slice(&c[..sf.n_total]);
{
let mut y_transition = vec![0.0f64; m];
match reconcile_final_basis_state(
&a_ext, &b, &c_phase2, &basis, &mut x_b, &mut y_transition,
options.max_etas, options.deadline,
) {
Ok(()) => {}
Err(crate::error::SolverError::DeadlineExceeded) => {
let solution = extract_timeout_solution_reconciled(
sf, &a_ext, &b, &c_phase2, &basis, &x_b, &col_scale,
options.max_etas, options.deadline,
);
return SolverResult { status: SolveStatus::Timeout, objective: sf.obj_offset, solution, iterations: total_iters, ..Default::default() };
}
Err(_) => return SolverResult::numerical_error(),
}
}
for i in 0..m {
if x_b[i].abs() < PIVOT_TOL {
x_b[i] = PIVOT_TOL * (i as f64 + 1.0);
}
}
for v in x_b.iter_mut() {
if *v < 0.0 { *v = 0.0; }
}
let mut pricing2 = SteepestEdgePricing::new(n_ext);
match revised_simplex_core(
&a_ext,
&mut x_b,
&c_phase2,
&b,
&mut basis,
m,
n_ext,
sf.n_total,
&mut pricing2,
options,
&mut total_iters,
false,
) {
SimplexOutcome::Optimal(obj2, mut y) => {
match reconcile_final_basis_state(&a_ext, &b, &c_phase2, &basis, &mut x_b, &mut y, options.max_etas, options.deadline) {
Ok(()) => {}
Err(crate::error::SolverError::DeadlineExceeded) => {
let solution = extract_timeout_solution_reconciled(
sf,
&a_ext,
&b,
&c_phase2,
&basis,
&x_b,
&col_scale,
options.max_etas,
options.deadline,
);
return SolverResult { status: SolveStatus::Timeout, objective: obj2 + sf.obj_offset, solution, iterations: total_iters, ..Default::default() };
}
Err(_) => return SolverResult::numerical_error(),
}
let solution = extract_solution(sf, &basis, &x_b, &col_scale);
if !check_eq_feasibility(problem, &solution) {
return SolverResult::numerical_error();
}
let (dual_solution, reduced_costs, slack) =
extract_dual_info(sf, problem, &y, &solution, &row_scale);
let ws = WarmStartBasis { basis: basis.clone(), x_b: x_b.clone() };
return SolverResult {
status: SolveStatus::Optimal,
objective: obj2 + sf.obj_offset,
solution,
dual_solution,
reduced_costs,
slack,
warm_start_basis: Some(ws),
..Default::default()
};
}
SimplexOutcome::Timeout(obj2) => {
let solution = extract_timeout_solution_reconciled(
sf,
&a_ext,
&b,
&c_phase2,
&basis,
&x_b,
&col_scale,
options.max_etas,
options.deadline,
);
return SolverResult {
status: SolveStatus::Timeout,
objective: obj2 + sf.obj_offset,
solution,
iterations: total_iters,
..Default::default()
};
}
SimplexOutcome::Unbounded => {
return SolverResult {
status: SolveStatus::Unbounded,
objective: f64::NEG_INFINITY,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
..Default::default()
};
}
SimplexOutcome::SingularBasis => {
return SolverResult::numerical_error();
}
}
}
SolverResult {
status: SolveStatus::Timeout,
objective: obj1 + sf.obj_offset,
solution: vec![],
dual_solution: vec![],
reduced_costs: vec![],
slack: vec![],
warm_start_basis: None,
iterations: total_iters,
..Default::default()
}
}
SimplexOutcome::SingularBasis => {
SolverResult::numerical_error()
}
}
}
}
const CRASH_REVERT_MAX_ROUNDS: usize = 3;
fn try_apply_crash(
a_ext: &CscMatrix,
m: usize,
n_shifted: usize,
n_total: usize,
b_scaled: &[f64],
max_etas: usize,
cold_basis: &[usize],
) -> Option<(Vec<usize>, Vec<f64>)> {
use crate::basis::{BasisManager, LuBasis};
use crate::sparse::SparseVec;
use super::crash;
use crate::tolerances::PIVOT_TOL;
let needs_artificial: Vec<bool> = cold_basis.iter().map(|&c| c >= n_total).collect();
let num_art_in = needs_artificial.iter().filter(|&&v| v).count();
if num_art_in == 0 {
return None;
}
let (mut basis, _, num_art_out) = crash::compute_crash_basis(
a_ext, b_scaled, m, n_shifted, cold_basis, &needs_artificial,
);
if num_art_out == num_art_in {
return None;
}
let mut x_b = vec![0.0_f64; m];
let mut crashed_count = num_art_in - num_art_out;
for round in 0..=CRASH_REVERT_MAX_ROUNDS {
let mut basis_mgr = match LuBasis::new(a_ext, &basis, max_etas) {
Ok(b) => b,
Err(_) => {
return None;
}
};
let mut x_b_sv = SparseVec::from_dense(b_scaled);
basis_mgr.ftran(&mut x_b_sv);
x_b = x_b_sv.to_dense();
let mut reverts = 0usize;
for i in 0..m {
if x_b[i] >= -PIVOT_TOL {
continue;
}
if cold_basis[i] >= n_total {
basis[i] = cold_basis[i];
reverts += 1;
} else {
return None;
}
}
if reverts == 0 {
break;
}
crashed_count = crashed_count.saturating_sub(reverts);
if crashed_count == 0 {
return None;
}
if round == CRASH_REVERT_MAX_ROUNDS && reverts > 0 {
return None;
}
}
Some((basis, x_b))
}
fn check_eq_feasibility(problem: &LpProblem, solution: &[f64]) -> bool {
let tol = feas_rel_tol();
let mut ax = vec![0.0f64; problem.num_constraints];
for (j, &sj) in solution.iter().enumerate() {
if let Ok((rows, vals)) = problem.a.get_column(j) {
for (k, &row) in rows.iter().enumerate() {
ax[row] += vals[k] * sj;
}
}
}
let mut violated = false;
for ((ax_i, ct), bi) in ax.iter().zip(problem.constraint_types.iter()).zip(problem.b.iter()) {
let violation = match ct {
ConstraintType::Eq => (ax_i - bi).abs(),
ConstraintType::Le => (ax_i - bi).max(0.0),
ConstraintType::Ge => (bi - ax_i).max(0.0),
};
let scale = 1.0 + bi.abs() + ax_i.abs();
let rel = violation / scale;
if rel > tol {
violated = true;
}
}
!violated
}
fn pivot_out_degenerate_artificials(
a_ext: &CscMatrix,
basis: &mut [usize],
x_b: &[f64],
sf: &StandardForm,
options: &SolverOptions,
) {
let m = basis.len();
if !basis.iter().zip(x_b.iter()).any(|(&col, &val)| col >= sf.n_total && val.abs() < PIVOT_TOL) {
#[cfg(test)]
PIVOT_CLEAN_EARLY_EXIT_COUNT.fetch_add(1, Ordering::Relaxed);
return;
}
#[cfg(test)]
PIVOT_CLEAN_CLEANUP_RAN_COUNT.fetch_add(1, Ordering::Relaxed);
let basis_before = basis.to_vec();
let mut basis_mgr = match LuBasis::new_timed(a_ext, basis, options.max_etas, options.deadline) {
Ok(mgr) => mgr,
Err(_) => return,
};
let mut is_basic = vec![false; a_ext.ncols];
for &col in basis.iter() {
is_basic[col] = true;
}
let mut z_dense = vec![0.0_f64; m];
for i in 0..m {
if options.deadline.is_some_and(|d| std::time::Instant::now() >= d) {
return;
}
if basis[i] < sf.n_total || x_b[i].abs() >= PIVOT_TOL {
continue;
}
z_dense.iter_mut().for_each(|v| *v = 0.0);
z_dense[i] = 1.0;
basis_mgr.btran_dense(&mut z_dense);
let mut best_j: Option<usize> = None;
let mut best_abs = PIVOT_TOL;
for j in 0..sf.n_total {
if is_basic[j] {
continue;
}
if let Ok((rows, vals)) = a_ext.get_column(j) {
let mut d_ij = 0.0_f64;
for (k, &row) in rows.iter().enumerate() {
if row < m {
d_ij += z_dense[row] * vals[k];
}
}
let abs_d = d_ij.abs();
if abs_d > best_abs {
best_abs = abs_d;
best_j = Some(j);
}
}
}
if let Some(j) = best_j {
let (col_rows, col_vals) = match a_ext.get_column(j) {
Ok(t) => t,
Err(_) => continue,
};
let mut d_sv = SparseVec {
indices: col_rows.to_vec(),
values: col_vals.to_vec(),
len: m,
};
basis_mgr.ftran(&mut d_sv);
is_basic[basis[i]] = false;
is_basic[j] = true;
basis[i] = j;
basis_mgr.update(j, i, &d_sv);
basis_mgr.refactor_if_needed_timed(a_ext, basis, options.deadline);
}
}
if LuBasis::new_timed(a_ext, basis, options.max_etas, options.deadline).is_err() {
basis.copy_from_slice(&basis_before);
}
}
pub(crate) fn reconcile_final_basis_state(
a: &CscMatrix,
b: &[f64],
c: &[f64],
basis: &[usize],
x_b: &mut [f64],
y: &mut [f64],
max_etas: usize,
deadline: Option<std::time::Instant>,
) -> Result<(), crate::error::SolverError> {
let mut basis_mgr = LuBasis::new_timed(a, basis, max_etas, deadline)?;
x_b.copy_from_slice(b);
basis_mgr.ftran_dense(x_b);
for value in x_b.iter_mut() {
if value.abs() < 1e-12 {
*value = 0.0;
}
}
compute_dual_vars_into(c, &mut basis_mgr, basis, y);
Ok(())
}
pub(crate) fn extract_solution(sf: &StandardForm, basis: &[usize], x_b: &[f64], col_scale: &[f64]) -> Vec<f64> {
use twofloat::TwoFloat;
let mut x_new = vec![0.0; sf.n_shifted];
for i in 0..sf.m {
if basis[i] < sf.n_shifted {
let scale = col_scale.get(basis[i]).copied().unwrap_or(1.0);
x_new[basis[i]] = x_b[i] * scale;
}
}
let mut solution = vec![0.0; sf.n_orig];
for (j, sol_j) in solution.iter_mut().enumerate() {
let info = &sf.orig_var_info[j];
let mut value = TwoFloat::from(info.offset);
for &(new_idx, coeff) in &info.new_vars {
value += TwoFloat::new_mul(coeff, x_new[new_idx]);
}
*sol_j = f64::from(value);
}
solution
}
const BAIL_TRIGGER_FACTOR: usize = 10;
const BAIL_TRIGGER_MIN: usize = 5_000;
const STEP_BAIL_RATIO: usize = 10;
const NO_PROGRESS_REL_EPS: f64 = 1e-12;
#[allow(clippy::too_many_arguments)]
pub(crate) fn revised_simplex_core<P: PricingStrategy>(
a: &CscMatrix,
x_b: &mut [f64],
c: &[f64],
b_rhs: &[f64],
basis: &mut [usize],
m: usize,
n_cols: usize,
n_price: usize,
pricing: &mut P,
options: &SolverOptions,
iter_count_out: &mut usize,
enable_phase1_cycling_bail: bool,
) -> SimplexOutcome {
let max_iter = usize::MAX; let mut basis_mgr = match LuBasis::new(a, basis, options.max_etas) {
Ok(bm) => bm,
Err(crate::error::SolverError::SingularBasis { .. }) => {
return SimplexOutcome::SingularBasis;
}
Err(_) => {
let obj: f64 = basic_obj(c, basis, x_b);
return SimplexOutcome::Timeout(obj);
}
};
let mut is_basic = vec![false; n_cols];
for &b in basis.iter() {
is_basic[b] = true;
}
let mut y_dense = vec![0.0f64; m];
let mut d_dense = vec![0.0f64; m];
let mut rc_vec = vec![0.0f64; n_price];
let mut blocked_at_basis: std::collections::HashSet<usize> = std::collections::HashSet::new();
let mut consecutive_blocks: usize = 0;
let max_consecutive_blocks: usize = m;
let mut stable_mode: bool = false;
let mut basis_snapshot: Vec<usize> = basis.to_vec();
let obj_bail_trigger = (BAIL_TRIGGER_FACTOR * m).max(BAIL_TRIGGER_MIN);
let step_bail_trigger = obj_bail_trigger / STEP_BAIL_RATIO;
let step_zero_threshold = PIVOT_TOL * (m as f64).max(1.0);
let mut best_obj: f64 = basic_obj(c, basis, x_b);
let mut iters_since_obj_progress: usize = 0;
let mut iters_since_step_progress: usize = 0;
for _iter in 0..max_iter {
*iter_count_out = iter_count_out.saturating_add(1);
let timed_out = options.deadline.is_some_and(|d| std::time::Instant::now() >= d);
let cancelled = options.cancel_flag.as_ref().is_some_and(|f| f.load(Ordering::Relaxed));
if timed_out || cancelled {
let obj: f64 = basic_obj(c, basis, x_b);
return SimplexOutcome::Timeout(obj);
}
compute_reduced_costs_into(
a, c, &mut basis_mgr, &is_basic, n_price, basis, &mut y_dense, &mut rc_vec,
);
for &j in &blocked_at_basis {
if j < n_price {
rc_vec[j] = 0.0;
}
}
let entering_col = match pricing.select_entering(&rc_vec, n_price) {
None => {
let obj: f64 = basic_obj(c, basis, x_b);
return SimplexOutcome::Optimal(obj, y_dense.clone());
}
Some(j) => j,
};
let (col_rows, col_vals) = a.get_column(entering_col).unwrap();
let orig_col_norm = col_vals.iter().cloned().fold(0.0f64, |acc, v| acc.max(v.abs()));
let mut d_sv = SparseVec {
indices: col_rows.to_vec(),
values: col_vals.to_vec(),
len: m,
};
basis_mgr.ftran(&mut d_sv);
d_sv.to_dense_into(&mut d_dense);
{
let d_max_abs = d_dense.iter().cloned().fold(0.0f64, |acc, v| {
if v.is_finite() { acc.max(v.abs()) } else { f64::INFINITY }
});
let d_corrupt = !d_max_abs.is_finite()
|| (orig_col_norm > 0.0 && d_max_abs > 1e12 * orig_col_norm);
if d_corrupt && basis_mgr.eta_count() > 0 {
basis_mgr.force_refactor_timed(a, basis, options.deadline);
if basis_mgr.refactor_failed {
if basis_mgr.singular_basis {
blocked_at_basis.insert(entering_col);
consecutive_blocks += 1;
if consecutive_blocks > max_consecutive_blocks {
return SimplexOutcome::SingularBasis;
}
stable_mode = true;
if !revert_to_snapshot(
a, basis, x_b, b_rhs, &basis_snapshot,
&mut is_basic, &mut basis_mgr, options,
) {
return SimplexOutcome::SingularBasis;
}
continue;
} else {
let obj: f64 = basic_obj(c, basis, x_b);
return SimplexOutcome::Timeout(obj);
}
}
let (cr2, cv2) = a.get_column(entering_col).unwrap();
d_sv = SparseVec { indices: cr2.to_vec(), values: cv2.to_vec(), len: m };
basis_mgr.ftran(&mut d_sv);
d_sv.to_dense_into(&mut d_dense);
basis_snapshot.copy_from_slice(basis);
}
}
let d = &d_dense;
let max_d_abs = d.iter().cloned().fold(0.0f64, |acc, v| acc.max(v.abs()));
let stable_floor = if stable_mode {
(PIVOT_STABILITY_THRESHOLD * max_d_abs).max(PIVOT_TOL)
} else {
PIVOT_TOL
};
let mut min_ratio = f64::INFINITY;
for i in 0..m {
if d[i] > stable_floor {
let ratio = x_b[i] / d[i];
if ratio < min_ratio {
min_ratio = ratio;
}
}
}
let effective_floor = if min_ratio.is_finite() {
stable_floor
} else if stable_mode {
for i in 0..m {
if d[i] > PIVOT_TOL {
let ratio = x_b[i] / d[i];
if ratio < min_ratio { min_ratio = ratio; }
}
}
PIVOT_TOL
} else {
PIVOT_TOL
};
if !min_ratio.is_finite() {
return SimplexOutcome::Unbounded;
}
let harris_window = min_ratio + PIVOT_TOL;
let mut leaving: Option<usize> = None;
let mut best_pivot_abs = 0.0f64;
for i in 0..m {
if d[i] > effective_floor {
let ratio = x_b[i] / d[i];
if ratio <= harris_window {
let d_abs = d[i].abs();
if d_abs > best_pivot_abs + PIVOT_TOL {
best_pivot_abs = d_abs;
leaving = Some(i);
} else if (d_abs - best_pivot_abs).abs() <= PIVOT_TOL {
match leaving {
None => leaving = Some(i),
Some(prev) if basis[i] < basis[prev] => leaving = Some(i),
_ => {}
}
}
}
}
}
let leaving_row = match leaving {
None => return SimplexOutcome::Unbounded,
Some(i) => i,
};
let step = x_b[leaving_row] / d[leaving_row];
for i in 0..m {
x_b[i] -= d[i] * step;
}
x_b[leaving_row] = step;
for val in x_b.iter_mut() {
if val.abs() < options.clamp_tol {
*val = 0.0;
}
}
let leaving_col = basis[leaving_row];
pricing.update_weights(&basis_mgr, entering_col, leaving_col, d);
is_basic[leaving_col] = false;
is_basic[entering_col] = true;
basis[leaving_row] = entering_col;
let pivot_unstable = d[leaving_row].abs() < PIVOT_STABILITY_THRESHOLD * max_d_abs
&& basis_mgr.eta_count() > 0;
if pivot_unstable {
basis_mgr.force_refactor_timed(a, basis, options.deadline);
} else {
basis_mgr.update(entering_col, leaving_row, &d_sv);
basis_mgr.refactor_if_needed_timed(a, basis, options.deadline);
}
if basis_mgr.refactor_failed {
if basis_mgr.singular_basis {
blocked_at_basis.insert(entering_col);
consecutive_blocks += 1;
if consecutive_blocks > max_consecutive_blocks {
return SimplexOutcome::SingularBasis;
}
stable_mode = true;
if !revert_to_snapshot(
a, basis, x_b, b_rhs, &basis_snapshot,
&mut is_basic, &mut basis_mgr, options,
) {
return SimplexOutcome::SingularBasis;
}
continue;
} else {
let obj: f64 = basic_obj(c, basis, x_b);
return SimplexOutcome::Timeout(obj);
}
}
if basis_mgr.eta_count() == 0 {
basis_snapshot.copy_from_slice(basis);
if !blocked_at_basis.is_empty() {
blocked_at_basis.clear();
consecutive_blocks = 0;
}
}
let current_obj: f64 = basic_obj(c, basis, x_b);
let progress_eps = best_obj.abs().max(1.0) * NO_PROGRESS_REL_EPS;
if current_obj + progress_eps < best_obj {
best_obj = current_obj;
iters_since_obj_progress = 0;
#[cfg(test)]
OBJ_PROGRESS_RESET_COUNT.fetch_add(1, Ordering::Relaxed);
} else {
iters_since_obj_progress = iters_since_obj_progress.saturating_add(1);
}
if step.abs() > step_zero_threshold {
iters_since_step_progress = 0;
} else {
iters_since_step_progress = iters_since_step_progress.saturating_add(1);
}
if enable_phase1_cycling_bail
&& iters_since_obj_progress >= obj_bail_trigger
&& iters_since_step_progress >= step_bail_trigger
{
return SimplexOutcome::Timeout(current_obj);
}
}
let obj: f64 = basic_obj(c, basis, x_b);
SimplexOutcome::Timeout(obj)
}
fn revert_to_snapshot(
a: &CscMatrix,
basis: &mut [usize],
x_b: &mut [f64],
b_rhs: &[f64],
basis_snapshot: &[usize],
is_basic: &mut [bool],
basis_mgr: &mut LuBasis,
options: &SolverOptions,
) -> bool {
basis.copy_from_slice(basis_snapshot);
for v in is_basic.iter_mut() { *v = false; }
for &col in basis.iter() {
is_basic[col] = true;
}
match LuBasis::new(a, basis, options.max_etas) {
Ok(mut mgr) => {
x_b.copy_from_slice(b_rhs);
mgr.ftran_dense(x_b);
for v in x_b.iter_mut() {
if v.abs() < options.clamp_tol { *v = 0.0; }
}
*basis_mgr = mgr;
true
}
Err(_) => false,
}
}