use oximo_core::{ConstraintId, Model, SocConstraintId, VarId};
use crate::result::SolverResult;
use crate::solver::Solver;
use crate::status::SolverError;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VarBoundKind {
Lower,
Upper,
}
#[derive(Clone, Debug, Default)]
pub struct Iis {
pub constraints: Vec<ConstraintId>,
pub soc_constraints: Vec<SocConstraintId>,
pub var_bounds: Vec<(VarId, VarBoundKind)>,
}
impl Iis {
#[must_use]
pub fn is_empty(&self) -> bool {
self.constraints.is_empty() && self.soc_constraints.is_empty() && self.var_bounds.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.constraints.len() + self.soc_constraints.len() + self.var_bounds.len()
}
#[must_use]
pub fn report<'a>(&'a self, model: &'a Model) -> IisReport<'a> {
IisReport { iis: self, model }
}
}
#[derive(Debug)]
pub struct IisReport<'a> {
iis: &'a Iis,
model: &'a Model,
}
impl std::fmt::Display for IisReport<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let iis = self.iis;
let m = self.model;
writeln!(f, "irreducible infeasible subsystem ({} members)", iis.len())?;
if !iis.constraints.is_empty() {
let cons = m.constraints();
writeln!(f, "\nconstraints ({})", iis.constraints.len())?;
for id in &iis.constraints {
match cons.get(id.index()) {
Some(c) => writeln!(f, " {}", c.name)?,
None => writeln!(f, " <constraint #{}>", id.index())?,
}
}
}
if !iis.soc_constraints.is_empty() {
let socs = m.soc_constraints();
writeln!(f, "\nsoc constraints ({})", iis.soc_constraints.len())?;
for id in &iis.soc_constraints {
match socs.get(id.index()) {
Some(s) => writeln!(f, " {}", s.name)?,
None => writeln!(f, " <soc #{}>", id.index())?,
}
}
}
if !iis.var_bounds.is_empty() {
let vars = m.variables();
writeln!(f, "\nvariable bounds ({})", iis.var_bounds.len())?;
for (id, kind) in &iis.var_bounds {
let side = match kind {
VarBoundKind::Lower => "lower",
VarBoundKind::Upper => "upper",
};
match vars.get(id.index()) {
Some(v) => writeln!(f, " {} ({side} bound)", v.name)?,
None => writeln!(f, " <var #{}> ({side} bound)", id.index())?,
}
}
}
Ok(())
}
}
pub trait InfeasibilityDiagnosis: Solver {
fn compute_iis(&mut self, model: &Model, opts: &Self::Options) -> Result<Iis, SolverError>;
}
#[must_use]
pub fn is_infeasible(result: &SolverResult) -> bool {
result.termination.is_infeasible()
}
#[cfg(test)]
mod tests {
use oximo_core::{constraint, variable};
use super::*;
#[test]
fn report_names_members() {
let m = Model::new("infeas");
variable!(m, x >= 0.0);
let lo = constraint!(m, floor, x >= 2.0);
let hi = constraint!(m, ceil, x <= 1.0);
let iis = Iis {
constraints: vec![lo, hi],
soc_constraints: Vec::new(),
var_bounds: vec![(x.var_id().unwrap(), VarBoundKind::Lower)],
};
assert_eq!(iis.len(), 3);
assert!(!iis.is_empty());
let out = iis.report(&m).to_string();
assert!(out.contains("irreducible infeasible subsystem (3 members)"), "{out}");
assert!(out.contains("floor"), "{out}");
assert!(out.contains("ceil"), "{out}");
assert!(out.contains("x (lower bound)"), "{out}");
}
#[test]
fn empty_iis_reports_zero() {
let m = Model::new("ok");
let iis = Iis::default();
assert!(iis.is_empty());
assert_eq!(iis.len(), 0);
let out = iis.report(&m).to_string();
assert!(out.contains("(0 members)"), "{out}");
}
}