ommx/
instance.rs

1use crate::{
2    parse::{Parse, ParseError, RawParseError},
3    v1::{self},
4    Constraint, ConstraintID, DecisionVariable, Function, RemovedConstraint, VariableID,
5};
6use std::collections::{BTreeSet, HashMap};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub enum Sense {
10    Minimize,
11    Maximize,
12}
13
14impl Parse for v1::instance::Sense {
15    type Output = Sense;
16    type Context = ();
17    fn parse(self, _: &Self::Context) -> Result<Self::Output, ParseError> {
18        match self {
19            v1::instance::Sense::Minimize => Ok(Sense::Minimize),
20            v1::instance::Sense::Maximize => Ok(Sense::Maximize),
21            v1::instance::Sense::Unspecified => Err(RawParseError::UnspecifiedEnum {
22                enum_name: "ommx.v1.instance.Sense",
23            }
24            .into()),
25        }
26    }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct OneHot {
31    pub id: ConstraintID,
32    pub variables: BTreeSet<VariableID>,
33}
34
35impl Parse for v1::OneHot {
36    type Output = OneHot;
37    type Context = (
38        HashMap<VariableID, DecisionVariable>,
39        HashMap<ConstraintID, Constraint>,
40    );
41    fn parse(
42        self,
43        (decision_variable, constraints): &Self::Context,
44    ) -> Result<Self::Output, ParseError> {
45        let message = "ommx.v1.OneHot";
46        let constraint_id = as_constraint_id(constraints, self.constraint_id)
47            .map_err(|e| e.context(message, "constraint_id"))?;
48        let mut variables = BTreeSet::new();
49        for v in &self.decision_variables {
50            let id = as_variable_id(decision_variable, *v)
51                .map_err(|e| e.context(message, "decision_variables"))?;
52            if !variables.insert(id) {
53                return Err(RawParseError::NonUniqueVariableID { id }
54                    .context(message, "decision_variables"));
55            }
56        }
57        Ok(OneHot {
58            id: constraint_id,
59            variables,
60        })
61    }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct Sos1 {
66    pub binary_constraint_id: ConstraintID,
67    pub big_m_constraint_ids: BTreeSet<ConstraintID>,
68    pub variables: BTreeSet<VariableID>,
69}
70
71impl Parse for v1::Sos1 {
72    type Output = Sos1;
73    type Context = (
74        HashMap<VariableID, DecisionVariable>,
75        HashMap<ConstraintID, Constraint>,
76    );
77    fn parse(
78        self,
79        (decision_variable, constraints): &Self::Context,
80    ) -> Result<Self::Output, ParseError> {
81        let message = "ommx.v1.Sos1";
82        let binary_constraint_id = as_constraint_id(constraints, self.binary_constraint_id)
83            .map_err(|e| e.context(message, "binary_constraint_id"))?;
84        let mut big_m_constraint_ids = BTreeSet::new();
85        for id in &self.big_m_constraint_ids {
86            let id = as_constraint_id(constraints, *id)
87                .map_err(|e| e.context(message, "big_m_constraint_ids"))?;
88            if !big_m_constraint_ids.insert(id) {
89                return Err(RawParseError::NonUniqueConstraintID { id }
90                    .context(message, "big_m_constraint_ids"));
91            }
92        }
93        let mut variables = BTreeSet::new();
94        for id in &self.decision_variables {
95            let id = as_variable_id(decision_variable, *id)
96                .map_err(|e| e.context(message, "decision_variables"))?;
97            if !variables.insert(id) {
98                return Err(RawParseError::NonUniqueVariableID { id }
99                    .context(message, "decision_variables"));
100            }
101        }
102        Ok(Sos1 {
103            binary_constraint_id,
104            big_m_constraint_ids,
105            variables,
106        })
107    }
108}
109
110#[derive(Debug, Default, Clone, PartialEq, Eq)]
111pub struct ConstraintHints {
112    pub one_hot_constraints: Vec<OneHot>,
113    pub sos1_constraints: Vec<Sos1>,
114}
115
116impl Parse for v1::ConstraintHints {
117    type Output = ConstraintHints;
118    type Context = (
119        HashMap<VariableID, DecisionVariable>,
120        HashMap<ConstraintID, Constraint>,
121    );
122    fn parse(self, context: &Self::Context) -> Result<Self::Output, ParseError> {
123        let message = "ommx.v1.ConstraintHints";
124        let one_hot_constraints = self
125            .one_hot_constraints
126            .into_iter()
127            .map(|c| c.parse_as(context, message, "one_hot_constraints"))
128            .collect::<Result<Vec<_>, ParseError>>()?;
129        let sos1_constraints = self
130            .sos1_constraints
131            .into_iter()
132            .map(|c| c.parse_as(context, message, "sos1_constraints"))
133            .collect::<Result<_, ParseError>>()?;
134        Ok(ConstraintHints {
135            one_hot_constraints,
136            sos1_constraints,
137        })
138    }
139}
140
141/// Instance, represents a mathematical optimization problem.
142///
143/// Invariants
144/// -----------
145/// - All `VariableID`s in `Function`s contained both directly and indirectly must be keys of `decision_variables`.
146/// - Key of `constraints` and `removed_constraints` are disjoint.
147/// - The keys of `decision_variable_dependency` are also keys of `decision_variables`.
148///
149#[derive(Debug, Clone, PartialEq)]
150pub struct Instance {
151    sense: Sense,
152    objective: Function,
153    decision_variables: HashMap<VariableID, DecisionVariable>,
154    constraints: HashMap<ConstraintID, Constraint>,
155    removed_constraints: HashMap<ConstraintID, RemovedConstraint>,
156    decision_variable_dependency: HashMap<VariableID, Function>,
157    parameters: Option<v1::Parameters>,
158    description: Option<v1::instance::Description>,
159    constraint_hints: ConstraintHints,
160}
161
162impl TryFrom<v1::Instance> for Instance {
163    type Error = ParseError;
164    fn try_from(value: v1::Instance) -> Result<Self, Self::Error> {
165        let message = "ommx.v1.Instance";
166        let sense = value.sense().parse_as(&(), message, "sense")?;
167
168        let decision_variables =
169            value
170                .decision_variables
171                .parse_as(&(), message, "decision_variables")?;
172
173        let objective = value
174            .objective
175            .ok_or(RawParseError::MissingField {
176                message,
177                field: "objective",
178            })?
179            .parse_as(&(), message, "objective")?;
180
181        let constraints = value.constraints.parse_as(&(), message, "constraints")?;
182        let removed_constraints =
183            value
184                .removed_constraints
185                .parse_as(&constraints, message, "removed_constraints")?;
186
187        let mut decision_variable_dependency = HashMap::new();
188        for (id, f) in value.decision_variable_dependency {
189            decision_variable_dependency.insert(
190                as_variable_id(&decision_variables, id)
191                    .map_err(|e| e.context(message, "decision_variable_dependency"))?,
192                f.parse_as(&(), message, "decision_variable_dependency")?,
193            );
194        }
195
196        let context = (decision_variables, constraints);
197        let constraint_hints = if let Some(hints) = value.constraint_hints {
198            hints.parse_as(&context, message, "constraint_hints")?
199        } else {
200            Default::default()
201        };
202        let (decision_variables, constraints) = context;
203
204        Ok(Self {
205            sense,
206            objective,
207            constraints,
208            decision_variables,
209            removed_constraints,
210            decision_variable_dependency,
211            parameters: value.parameters,
212            description: value.description,
213            constraint_hints,
214        })
215    }
216}
217
218fn as_constraint_id(
219    constraints: &HashMap<ConstraintID, Constraint>,
220    id: u64,
221) -> Result<ConstraintID, ParseError> {
222    let id = ConstraintID::from(id);
223    if !constraints.contains_key(&id) {
224        return Err(RawParseError::UndefinedConstraintID { id }.into());
225    }
226    Ok(id)
227}
228
229fn as_variable_id(
230    decision_variables: &HashMap<VariableID, DecisionVariable>,
231    id: u64,
232) -> Result<VariableID, ParseError> {
233    let id = VariableID::from(id);
234    if !decision_variables.contains_key(&id) {
235        return Err(RawParseError::UndefinedVariableID { id }.into());
236    }
237    Ok(id)
238}