Skip to main content

russcip/
variable.rs

1use crate::scip::ScipPtr;
2use crate::{Col, ffi};
3use core::panic;
4use scip_sys::SCIP_Status;
5use std::rc::Rc;
6
7/// A type alias for a variable ID.
8pub type VarId = usize;
9
10/// A wrapper for a mutable reference to a SCIP variable.
11#[derive(Debug, Clone)]
12#[allow(dead_code)]
13pub struct Variable {
14    pub(crate) raw: *mut ffi::SCIP_VAR,
15    pub(crate) scip: Rc<ScipPtr>,
16}
17
18impl PartialEq for Variable {
19    fn eq(&self, other: &Self) -> bool {
20        self.index() == other.index() && self.raw == other.raw
21    }
22}
23
24impl Eq for Variable {}
25
26impl Variable {
27    /// Returns a raw pointer to the underlying `ffi::SCIP_VAR` struct.
28    pub fn inner(&self) -> *mut ffi::SCIP_VAR {
29        self.raw
30    }
31
32    /// Returns the index of the variable.
33    pub fn index(&self) -> usize {
34        let id = unsafe { ffi::SCIPvarGetIndex(self.raw) };
35        assert!(id >= 0);
36        id as usize
37    }
38
39    /// Returns the name of the variable.
40    pub fn name(&self) -> String {
41        let name = unsafe { ffi::SCIPvarGetName(self.raw) };
42        let name = unsafe { std::ffi::CStr::from_ptr(name) };
43        name.to_str().unwrap().to_string()
44    }
45
46    /// Returns the objective coefficient of the variable.
47    pub fn obj(&self) -> f64 {
48        unsafe { ffi::SCIPvarGetObj(self.raw) }
49    }
50
51    /// Returns the lower bound of the variable.
52    pub fn lb(&self) -> f64 {
53        unsafe { ffi::SCIPvarGetLbLocal(self.raw) }
54    }
55
56    /// Returns the upper bound of the variable.
57    pub fn ub(&self) -> f64 {
58        unsafe { ffi::SCIPvarGetUbLocal(self.raw) }
59    }
60
61    /// Returns the local lower bound of the variable.
62    pub fn lb_local(&self) -> f64 {
63        unsafe { ffi::SCIPvarGetLbLocal(self.raw) }
64    }
65
66    /// Returns the local upper bound of the variable.
67    pub fn ub_local(&self) -> f64 {
68        unsafe { ffi::SCIPvarGetUbLocal(self.raw) }
69    }
70
71    /// Returns the type of the variable.
72    pub fn var_type(&self) -> VarType {
73        let var_type = unsafe { ffi::SCIPvarGetType(self.raw) };
74        var_type.into()
75    }
76
77    /// Returns the status of the variable.
78    pub fn status(&self) -> VarStatus {
79        let status = unsafe { ffi::SCIPvarGetStatus(self.raw) };
80        status.into()
81    }
82
83    /// Returns the column associated with the variable.
84    pub fn col(&self) -> Option<Col> {
85        if self.is_in_lp() {
86            let col_ptr = unsafe { ffi::SCIPvarGetCol(self.raw) };
87            let col = Col {
88                raw: col_ptr,
89                scip: Rc::clone(&self.scip),
90            };
91            Some(col)
92        } else {
93            None
94        }
95    }
96
97    /// Returns whether the variable is a column variable in the LP relaxation.
98    pub fn is_in_lp(&self) -> bool {
99        (unsafe { ffi::SCIPvarIsInLP(self.raw) }) != 0
100    }
101
102    /// Returns the solution value of the variable in the current node.
103    pub fn sol_val(&self) -> f64 {
104        unsafe { ffi::SCIPgetVarSol(self.scip.raw, self.raw) }
105    }
106
107    /// Returns whether the variable is deleted.
108    pub fn is_deleted(&self) -> bool {
109        unsafe { ffi::SCIPvarIsDeleted(self.raw) != 0 }
110    }
111
112    /// Returns whether the variable is transformed.
113    pub fn is_transformed(&self) -> bool {
114        unsafe { ffi::SCIPvarIsTransformed(self.raw) != 0 }
115    }
116
117    /// Returns whether the variable is original.
118    pub fn is_original(&self) -> bool {
119        unsafe { ffi::SCIPvarIsOriginal(self.raw) != 0 }
120    }
121
122    /// Returns whether the variable is negated.
123    pub fn is_negated(&self) -> bool {
124        unsafe { ffi::SCIPvarIsNegated(self.raw) != 0 }
125    }
126
127    /// Returns whether the variable is removable (due to aging in the LP).
128    pub fn is_removable(&self) -> bool {
129        unsafe { ffi::SCIPvarIsRemovable(self.raw) != 0 }
130    }
131
132    /// Returns whether the variable is a directed counterpart of an original variable.
133    pub fn is_trans_from_orig(&self) -> bool {
134        unsafe { ffi::SCIPvarIsTransformedOrigvar(self.raw) != 0 }
135    }
136
137    /// Returns whether the variable is active (i.e., neither fixed nor aggregated).
138    pub fn is_active(&self) -> bool {
139        unsafe { ffi::SCIPvarIsActive(self.raw) != 0 }
140    }
141
142    /// Returns the trasnformed variable if it exists.
143    pub fn transformed(&self) -> Option<Variable> {
144        let var_ptr = unsafe { ffi::SCIPvarGetTransVar(self.raw) };
145        if var_ptr.is_null() {
146            return None;
147        }
148
149        let var = Variable {
150            raw: var_ptr,
151            scip: Rc::clone(&self.scip),
152        };
153        Some(var)
154    }
155}
156
157/// The type of variable in an optimization problem.
158#[derive(Debug, PartialEq, Eq, Clone, Copy)]
159pub enum VarType {
160    /// The variable is a continuous variable.
161    Continuous,
162    /// The variable is an integer variable.
163    Integer,
164    /// The variable is a binary variable.
165    Binary,
166    /// The variable is an implicit integer variable.
167    ImplInt,
168}
169
170impl From<VarType> for ffi::SCIP_Vartype {
171    fn from(var_type: VarType) -> Self {
172        match var_type {
173            VarType::Continuous => ffi::SCIP_Vartype_SCIP_VARTYPE_CONTINUOUS,
174            VarType::Integer => ffi::SCIP_Vartype_SCIP_VARTYPE_INTEGER,
175            VarType::Binary => ffi::SCIP_Vartype_SCIP_VARTYPE_BINARY,
176            VarType::ImplInt => ffi::SCIP_Vartype_SCIP_VARTYPE_IMPLINT,
177        }
178    }
179}
180
181impl From<ffi::SCIP_Vartype> for VarType {
182    fn from(var_type: ffi::SCIP_Vartype) -> Self {
183        match var_type {
184            ffi::SCIP_Vartype_SCIP_VARTYPE_CONTINUOUS => VarType::Continuous,
185            ffi::SCIP_Vartype_SCIP_VARTYPE_INTEGER => VarType::Integer,
186            ffi::SCIP_Vartype_SCIP_VARTYPE_BINARY => VarType::Binary,
187            ffi::SCIP_Vartype_SCIP_VARTYPE_IMPLINT => VarType::ImplInt,
188            _ => panic!("Unknown VarType {:?}", var_type),
189        }
190    }
191}
192
193/// An enum representing the status of a SCIP variable.
194#[derive(Debug, PartialEq, Eq, Clone, Copy)]
195pub enum VarStatus {
196    /// The variable is an original variable in the problem.
197    Original,
198    /// The variable is a loose variable in the problem.
199    Loose,
200    /// The variable is a column variable in the problem.
201    Column,
202    /// The variable is a fixed variable in the problem.
203    Fixed,
204    /// The variable is an aggregated variable in the problem.
205    Aggregated,
206    /// The variable is a multi-aggregated variable in the problem.
207    MultiAggregated,
208    /// The variable is a negated variable in the problem.
209    NegatedVar,
210}
211
212impl From<SCIP_Status> for VarStatus {
213    fn from(status: SCIP_Status) -> Self {
214        match status {
215            ffi::SCIP_Varstatus_SCIP_VARSTATUS_ORIGINAL => VarStatus::Original,
216            ffi::SCIP_Varstatus_SCIP_VARSTATUS_LOOSE => VarStatus::Loose,
217            ffi::SCIP_Varstatus_SCIP_VARSTATUS_COLUMN => VarStatus::Column,
218            ffi::SCIP_Varstatus_SCIP_VARSTATUS_FIXED => VarStatus::Fixed,
219            ffi::SCIP_Varstatus_SCIP_VARSTATUS_AGGREGATED => VarStatus::Aggregated,
220            ffi::SCIP_Varstatus_SCIP_VARSTATUS_MULTAGGR => VarStatus::MultiAggregated,
221            ffi::SCIP_Varstatus_SCIP_VARSTATUS_NEGATED => VarStatus::NegatedVar,
222            _ => panic!("Unhandled SCIP variable status {:?}", status),
223        }
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::{Model, ObjSense, ProblemOrSolving, minimal_model};
231
232    #[test]
233    fn var_data() {
234        let mut model = Model::new().include_default_plugins().create_prob("test");
235        let var = model.add_var(0.0, 1.0, 2.0, "x", VarType::ImplInt);
236
237        assert_eq!(var.index(), 0);
238        assert_eq!(var.lb(), 0.0);
239        assert_eq!(var.lb_local(), 0.0);
240        assert_eq!(var.ub(), 1.0);
241        assert_eq!(var.ub_local(), 1.0);
242        assert_eq!(var.obj(), 2.0);
243        assert_eq!(var.name(), "x");
244        assert_eq!(var.var_type(), VarType::ImplInt);
245        assert_eq!(var.status(), VarStatus::Original);
246        assert!(!var.is_in_lp());
247        assert!(!var.is_deleted());
248        assert!(!var.is_transformed());
249        assert!(var.is_original());
250        assert!(!var.is_negated());
251        assert!(!var.is_removable());
252        assert!(var.is_active());
253
254        assert!(!var.inner().is_null());
255    }
256
257    #[test]
258    fn var_memory_safety() {
259        let mut model = Model::new()
260            .hide_output()
261            .include_default_plugins()
262            .create_prob("test")
263            .set_obj_sense(ObjSense::Maximize);
264
265        let x1 = model.add_var(0., f64::INFINITY, 3., "x1", VarType::Integer);
266
267        drop(model);
268        assert_eq!(x1.name(), "x1");
269    }
270
271    #[test]
272    fn var_sol_val() {
273        let mut model = minimal_model();
274        let x = model.add_var(0.0, 1.0, 1.0, "x", VarType::Binary);
275        let _cons = model.add_cons(vec![&x], &[1.0], 1.0, 1.0, "cons1");
276
277        model.solve();
278
279        assert_eq!(x.sol_val(), 1.0);
280    }
281}