1use std::fmt;
2
3use oximo_expr::{Expr, ExprId, ModelId};
4use smol_str::SmolStr;
5
6#[derive(Copy, Clone, Debug, PartialEq, Eq)]
8pub enum Sense {
9 Le,
10 Ge,
11 Eq,
12}
13
14impl fmt::Display for Sense {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 f.write_str(match self {
17 Self::Le => "<=",
18 Self::Ge => ">=",
19 Self::Eq => "=",
20 })
21 }
22}
23
24#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
25pub struct ConstraintId(pub u32);
26
27#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
33pub struct ConstraintHandle {
34 id: ConstraintId,
35 model_id: ModelId,
36}
37
38impl ConstraintHandle {
39 pub(crate) const fn new(id: ConstraintId, model_id: ModelId) -> Self {
40 Self { id, model_id }
41 }
42
43 #[must_use]
44 pub const fn id(self) -> ConstraintId {
45 self.id
46 }
47
48 #[must_use]
49 pub const fn model_id(self) -> ModelId {
50 self.model_id
51 }
52
53 #[must_use]
54 pub fn index(self) -> usize {
55 self.id.index()
56 }
57}
58
59impl From<ConstraintHandle> for ConstraintId {
60 fn from(value: ConstraintHandle) -> Self {
61 value.id
62 }
63}
64
65impl PartialEq<ConstraintId> for ConstraintHandle {
66 fn eq(&self, other: &ConstraintId) -> bool {
67 self.id == *other
68 }
69}
70
71impl PartialEq<ConstraintHandle> for ConstraintId {
72 fn eq(&self, other: &ConstraintHandle) -> bool {
73 *self == other.id
74 }
75}
76
77#[derive(Copy, Clone, Debug, PartialEq, Eq)]
82pub enum RangeConstraintIds {
83 Interval(ConstraintId),
84 Split { lower: ConstraintId, upper: ConstraintId },
85}
86
87#[derive(Copy, Clone, Debug, PartialEq, Eq)]
89pub enum RangeConstraintHandles {
90 Interval(ConstraintHandle),
91 Split { lower: ConstraintHandle, upper: ConstraintHandle },
92}
93
94impl RangeConstraintHandles {
95 #[must_use]
96 pub const fn ids(self) -> RangeConstraintIds {
97 match self {
98 Self::Interval(handle) => RangeConstraintIds::Interval(handle.id()),
99 Self::Split { lower, upper } => {
100 RangeConstraintIds::Split { lower: lower.id(), upper: upper.id() }
101 }
102 }
103 }
104}
105
106impl From<RangeConstraintHandles> for RangeConstraintIds {
107 fn from(value: RangeConstraintHandles) -> Self {
108 value.ids()
109 }
110}
111
112impl PartialEq<RangeConstraintIds> for RangeConstraintHandles {
113 fn eq(&self, other: &RangeConstraintIds) -> bool {
114 self.ids() == *other
115 }
116}
117
118impl PartialEq<RangeConstraintHandles> for RangeConstraintIds {
119 fn eq(&self, other: &RangeConstraintHandles) -> bool {
120 *self == other.ids()
121 }
122}
123
124impl ConstraintId {
125 #[inline]
126 pub fn index(self) -> usize {
127 self.0 as usize
128 }
129}
130
131#[derive(Clone, Debug)]
141pub struct Constraint {
142 pub name: SmolStr,
143 pub lhs: ExprId,
144 pub lower: f64,
145 pub upper: f64,
146 pub active: bool,
147}
148
149impl Constraint {
150 fn is_equality(&self) -> bool {
154 self.lower.total_cmp(&self.upper).is_eq()
155 }
156
157 #[must_use]
162 pub fn is_range(&self) -> bool {
163 self.lower.is_finite() && self.upper.is_finite() && !self.is_equality()
164 }
165
166 #[must_use]
170 pub fn as_single(&self) -> Option<(Sense, f64)> {
171 match (self.lower.is_finite(), self.upper.is_finite()) {
172 (false, true) => Some((Sense::Le, self.upper)),
173 (true, false) => Some((Sense::Ge, self.lower)),
174 (true, true) if self.is_equality() => Some((Sense::Eq, self.lower)),
175 _ => None,
176 }
177 }
178}
179
180#[derive(Copy, Clone, Debug)]
183pub struct ConstraintExpr<'a> {
184 pub lhs: Expr<'a>,
185 pub sense: Sense,
186 pub rhs: f64,
187}
188
189pub trait Relate<'a> {
191 fn le<R: IntoRhs<'a>>(self, rhs: R) -> ConstraintExpr<'a>;
192 fn ge<R: IntoRhs<'a>>(self, rhs: R) -> ConstraintExpr<'a>;
193 fn eq<R: IntoRhs<'a>>(self, rhs: R) -> ConstraintExpr<'a>;
194}
195
196pub trait IntoRhs<'a> {
199 fn fold_rhs(self, lhs: Expr<'a>) -> (Expr<'a>, f64);
200
201 fn const_bound(&self) -> Option<f64> {
207 None
208 }
209}
210
211impl<'a> IntoRhs<'a> for f64 {
212 fn fold_rhs(self, lhs: Expr<'a>) -> (Expr<'a>, f64) {
213 (lhs, self)
214 }
215 fn const_bound(&self) -> Option<f64> {
216 Some(*self)
217 }
218}
219
220impl<'a> IntoRhs<'a> for i32 {
221 fn fold_rhs(self, lhs: Expr<'a>) -> (Expr<'a>, f64) {
222 (lhs, f64::from(self))
223 }
224 fn const_bound(&self) -> Option<f64> {
225 Some(f64::from(*self))
226 }
227}
228
229impl<'a> IntoRhs<'a> for Expr<'a> {
230 fn fold_rhs(self, lhs: Expr<'a>) -> (Expr<'a>, f64) {
231 (lhs - self, 0.0)
232 }
233}
234
235impl<'a> Relate<'a> for Expr<'a> {
236 fn le<R: IntoRhs<'a>>(self, rhs: R) -> ConstraintExpr<'a> {
237 let (lhs, rhs) = rhs.fold_rhs(self);
238 ConstraintExpr { lhs, sense: Sense::Le, rhs }
239 }
240
241 fn ge<R: IntoRhs<'a>>(self, rhs: R) -> ConstraintExpr<'a> {
242 let (lhs, rhs) = rhs.fold_rhs(self);
243 ConstraintExpr { lhs, sense: Sense::Ge, rhs }
244 }
245
246 fn eq<R: IntoRhs<'a>>(self, rhs: R) -> ConstraintExpr<'a> {
247 let (lhs, rhs) = rhs.fold_rhs(self);
248 ConstraintExpr { lhs, sense: Sense::Eq, rhs }
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::Sense;
255
256 #[test]
257 fn display_uses_ascii_relations() {
258 let labels = [Sense::Le.to_string(), Sense::Ge.to_string(), Sense::Eq.to_string()];
259 assert_eq!(labels, ["<=", ">=", "="]);
260 assert!(labels.iter().all(|label| label.is_ascii()));
261 }
262}