Skip to main content

oximo_core/
indicator.rs

1use oximo_expr::{ExprId, ModelId, VarId};
2use smol_str::SmolStr;
3
4use crate::model::Model;
5
6/// Stable identifier for a native indicator constraint.
7#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
8pub struct IndicatorConstraintId(pub u32);
9
10impl IndicatorConstraintId {
11    #[inline]
12    pub fn index(self) -> usize {
13        self.0 as usize
14    }
15}
16
17/// A binary-triggered affine row, stored canonically as
18/// `trigger == active_value => lower <= lhs <= upper`.
19#[derive(Clone, Debug)]
20pub struct IndicatorConstraint {
21    pub name: SmolStr,
22    pub trigger: VarId,
23    pub active_value: bool,
24    pub lhs: ExprId,
25    pub lower: f64,
26    pub upper: f64,
27    pub active: bool,
28}
29
30impl IndicatorConstraint {
31    #[must_use]
32    pub fn is_range(&self) -> bool {
33        self.lower.is_finite()
34            && self.upper.is_finite()
35            && !self.lower.total_cmp(&self.upper).is_eq()
36    }
37
38    #[must_use]
39    pub fn as_single(&self) -> Option<(crate::Sense, f64)> {
40        match (self.lower.is_finite(), self.upper.is_finite()) {
41            (false, true) => Some((crate::Sense::Le, self.upper)),
42            (true, false) => Some((crate::Sense::Ge, self.lower)),
43            (true, true) if self.lower.total_cmp(&self.upper).is_eq() => {
44                Some((crate::Sense::Eq, self.lower))
45            }
46            _ => None,
47        }
48    }
49}
50
51/// Model-bound handle returned by a scalar indicator declaration.
52#[derive(Copy, Clone)]
53pub struct IndicatorConstraintHandle<'a> {
54    pub(crate) model: &'a Model,
55    pub(crate) id: IndicatorConstraintId,
56}
57
58impl std::fmt::Debug for IndicatorConstraintHandle<'_> {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        f.debug_struct("IndicatorConstraintHandle").field("id", &self.id).finish()
61    }
62}
63
64impl IndicatorConstraintHandle<'_> {
65    #[must_use]
66    pub const fn id(self) -> IndicatorConstraintId {
67        self.id
68    }
69    #[must_use]
70    pub fn index(self) -> usize {
71        self.id.index()
72    }
73    #[must_use]
74    pub fn model_id(self) -> ModelId {
75        self.model.id()
76    }
77}
78
79impl From<IndicatorConstraintHandle<'_>> for IndicatorConstraintId {
80    fn from(value: IndicatorConstraintHandle<'_>) -> Self {
81        value.id
82    }
83}
84
85/// IDs produced by a two-sided indicator declaration.
86#[derive(Copy, Clone, Debug, PartialEq, Eq)]
87pub enum RangeIndicatorConstraintIds {
88    Interval(IndicatorConstraintId),
89    Split { lower: IndicatorConstraintId, upper: IndicatorConstraintId },
90}
91
92/// Model-bound handles produced by a two-sided indicator declaration.
93#[derive(Copy, Clone, Debug)]
94pub enum RangeIndicatorConstraintHandles<'a> {
95    Interval(IndicatorConstraintHandle<'a>),
96    Split { lower: IndicatorConstraintHandle<'a>, upper: IndicatorConstraintHandle<'a> },
97}
98
99impl RangeIndicatorConstraintHandles<'_> {
100    #[must_use]
101    pub const fn ids(self) -> RangeIndicatorConstraintIds {
102        match self {
103            Self::Interval(h) => RangeIndicatorConstraintIds::Interval(h.id()),
104            Self::Split { lower, upper } => {
105                RangeIndicatorConstraintIds::Split { lower: lower.id(), upper: upper.id() }
106            }
107        }
108    }
109}