use oximo_expr::{
ExprArena, ExprId, LinearTerms, QuadraticTerms, VarId, extract_linear, extract_quadratic,
};
use smol_str::SmolStr;
use crate::constraint::{Constraint, Sense};
use crate::var::Variable;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SocConstraintId(pub u32);
impl SocConstraintId {
#[inline]
pub fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Debug)]
pub struct SocConstraint {
pub name: SmolStr,
pub terms: Vec<ExprId>,
pub bound: ExprId,
pub active: bool,
}
#[derive(Clone, Debug)]
pub struct SocForm {
pub terms: Vec<LinearTerms>,
pub bound: LinearTerms,
}
fn soc_quadratic(
arena: &ExprArena,
vars: &[Variable],
c: &Constraint,
) -> Option<(QuadraticTerms, VarId, f64)> {
let (sense, rhs) = c.as_single()?;
if sense != Sense::Le {
return None;
}
let q = extract_quadratic(arena, c.lhs)?;
if !q.linear.is_empty() || q.constant - rhs != 0.0 {
return None;
}
let mut positives = 0;
let mut negative: Option<(VarId, f64)> = None;
for &(row, col, h) in &q.hessian {
if row != col {
return None;
}
let coef = h / 2.0;
if coef > 0.0 {
positives += 1;
} else if coef < 0.0 {
if negative.is_some() {
return None;
}
negative = Some((row, -coef));
}
}
let (t, n) = negative?;
if positives == 0 || vars[t.index()].lb < 0.0 {
return None;
}
Some((q, t, n))
}
pub(crate) fn is_detected_soc(arena: &ExprArena, vars: &[Variable], c: &Constraint) -> bool {
soc_quadratic(arena, vars, c).is_some()
}
pub fn detect_soc(arena: &ExprArena, vars: &[Variable], c: &Constraint) -> Option<SocForm> {
let (q, t, n) = soc_quadratic(arena, vars, c)?;
let terms = q
.hessian
.into_iter()
.filter_map(|(row, _, h)| {
let coef = h / 2.0;
(coef > 0.0)
.then(|| LinearTerms { coeffs: vec![(row, (coef / n).sqrt())], constant: 0.0 })
})
.collect();
let bound = LinearTerms { coeffs: vec![(t, 1.0)], constant: 0.0 };
Some(SocForm { terms, bound })
}
pub fn explicit_soc_form(arena: &ExprArena, s: &SocConstraint) -> Option<SocForm> {
let terms = s.terms.iter().map(|&e| extract_linear(arena, e)).collect::<Option<Vec<_>>>()?;
let bound = extract_linear(arena, s.bound)?;
Some(SocForm { terms, bound })
}