Skip to main content

oximo_core/
var.rs

1use oximo_expr::{Expr, VarId};
2use smol_str::SmolStr;
3
4use crate::domain::Domain;
5use crate::model::Model;
6
7/// Variable metadata held by the [`Model`]. Users do not construct this
8/// directly, they get an [`Expr`] back from [`VarBuilder::build`] and look up
9/// solution values via [`crate::Model`] / `oximo_solver::SolverResult`.
10#[derive(Clone, Debug)]
11pub struct Variable {
12    pub id: VarId,
13    pub name: SmolStr,
14    pub domain: Domain,
15    pub lb: f64,
16    pub ub: f64,
17    pub initial: Option<f64>,
18}
19
20/// Display name of `v` within `vars`, degrading to `variable #<index>` when the
21/// id is out of range (a foreign or not-yet-registered [`VarId`]). Used to build
22/// human-readable error messages that name the offending variable.
23#[must_use]
24pub fn var_name(vars: &[Variable], v: VarId) -> String {
25    vars.get(v.index()).map_or_else(|| format!("variable #{}", v.index()), |x| x.name.to_string())
26}
27
28/// To be fixable,  the value is finite, integral when the domain is integer-valued,
29/// and within `[lb, ub]`. A semicontinuous/semiinteger variable is 0 or at least its
30/// threshold
31pub(crate) fn assert_fixable(name: &str, domain: Domain, lb: f64, ub: f64, value: f64) {
32    assert!(value.is_finite(), "cannot fix variable {name:?} to the non-finite value {value}");
33    assert!(
34        !domain.is_integer() || value.fract() == 0.0,
35        "cannot fix {domain} variable {name:?} to the fractional value {value}"
36    );
37    let semi_zero = domain.semi_threshold().is_some() && value == 0.0;
38    assert!(
39        semi_zero || (lb <= value && value <= ub),
40        "cannot fix variable {name:?} to {value}, outside its bounds [{lb}, {ub}]"
41    );
42    if let Some(threshold) = domain.semi_threshold() {
43        assert!(
44            value == 0.0 || value >= threshold,
45            "cannot fix {domain} variable {name:?} to {value}: it must be 0 or at least {threshold}"
46        );
47    }
48}
49
50/// Builder backing the `variable!` macro. Configure bounds / domain, then call
51/// [`Self::build`] to register the variable and obtain an `Expr` handle.
52#[must_use = "VarBuilder does nothing until you call .build()"]
53pub struct VarBuilder<'a> {
54    pub(crate) model: &'a Model,
55    pub(crate) name: SmolStr,
56    pub(crate) lb: f64,
57    pub(crate) ub: f64,
58    pub(crate) domain: Domain,
59    pub(crate) initial: Option<f64>,
60}
61
62impl<'a> std::fmt::Debug for VarBuilder<'a> {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        f.debug_struct("VarBuilder")
65            .field("name", &self.name)
66            .field("lb", &self.lb)
67            .field("ub", &self.ub)
68            .field("domain", &self.domain)
69            .finish()
70    }
71}
72
73impl<'a> VarBuilder<'a> {
74    pub fn lb(mut self, v: f64) -> Self {
75        self.lb = v;
76        self
77    }
78
79    pub fn ub(mut self, v: f64) -> Self {
80        self.ub = v;
81        self
82    }
83
84    pub fn bounds(mut self, lb: f64, ub: f64) -> Self {
85        self.lb = lb;
86        self.ub = ub;
87        self
88    }
89
90    /// Fix the variable to `value`, i.e. `bounds(value, value)`.
91    ///
92    /// # Panics
93    ///
94    /// Panics if `value` is not a feasible fixing for the domain and bounds set
95    /// so far: non-finite, fractional on an integer domain, outside the bounds,
96    /// or inside a semicontinuity gap. A `.domain(..)` or bound call placed after
97    /// this one is not re-checked; the `variable!` macro always emits them first.
98    pub fn fix(self, value: f64) -> Self {
99        assert_fixable(&self.name, self.domain, self.lb, self.ub, value);
100        self.bounds(value, value)
101    }
102
103    pub fn domain(mut self, d: Domain) -> Self {
104        self.domain = d;
105        self
106    }
107
108    pub fn integer(mut self) -> Self {
109        self.domain = Domain::Integer;
110        self
111    }
112
113    pub fn binary(mut self) -> Self {
114        self.domain = Domain::Binary;
115        self.lb = 0.0;
116        self.ub = 1.0;
117        self
118    }
119
120    pub fn initial(mut self, v: f64) -> Self {
121        self.initial = Some(v);
122        self
123    }
124
125    pub fn build(self) -> Expr<'a> {
126        self.model.register_var(self)
127    }
128}