use oximo_expr::{ExprArena, ExprId, LinearTerms, 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,
}
pub fn detect_soc(arena: &ExprArena, vars: &[Variable], c: &Constraint) -> Option<SocForm> {
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: Vec<(VarId, f64)> = Vec::new();
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.push((row, coef));
} else if coef < 0.0 {
if negative.is_some() {
return None;
}
negative = Some((row, -coef));
}
}
let (t, n) = negative?;
if positives.is_empty() || vars[t.index()].lb < 0.0 {
return None;
}
let terms = positives
.into_iter()
.map(|(x, p)| LinearTerms { coeffs: vec![(x, (p / 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 })
}