russcip/
constraint.rs

1use crate::scip::ScipPtr;
2use crate::{Row, ffi};
3use std::rc::Rc;
4
5/// A constraint in an optimization problem.
6#[derive(Debug, Clone)]
7#[allow(dead_code)]
8pub struct Constraint {
9    /// A pointer to the underlying `SCIP_CONS` C struct.
10    pub(crate) raw: *mut ffi::SCIP_CONS,
11    /// A reference to the SCIP instance that owns this constraint (to prevent freeing the model while the constraint is live).
12    pub(crate) scip: Rc<ScipPtr>,
13}
14
15impl Constraint {
16    /// Returns a pointer to the underlying `SCIP_CONS` C struct.
17    pub fn inner(&self) -> *mut ffi::SCIP_CONS {
18        self.raw
19    }
20
21    /// Returns the name of the constraint.
22    pub fn name(&self) -> String {
23        unsafe {
24            let name = ffi::SCIPconsGetName(self.raw);
25            String::from(std::ffi::CStr::from_ptr(name).to_str().unwrap())
26        }
27    }
28
29    /// Returns the row associated with the constraint.
30    pub fn row(&self) -> Option<Row> {
31        let row_ptr = unsafe { ffi::SCIPconsGetRow(self.scip.raw, self.raw) };
32        if row_ptr.is_null() {
33            None
34        } else {
35            Some(Row {
36                raw: row_ptr,
37                scip: Rc::clone(&self.scip),
38            })
39        }
40    }
41
42    /// Returns the dual solution of the linear constraint in the current LP.
43    /// Returns `None` if the constraint is not a linear constraint.
44    pub fn dual_sol(&self) -> Option<f64> {
45        let cons_handler = unsafe { ffi::SCIPconsGetHdlr(self.raw) };
46        if cons_handler.is_null() {
47            return None;
48        }
49        let cons_handler_name = unsafe { ffi::SCIPconshdlrGetName(cons_handler) };
50        if cons_handler_name.is_null() {
51            return None;
52        }
53        let cons_handler_name = unsafe { std::ffi::CStr::from_ptr(cons_handler_name) };
54        if cons_handler_name.to_str().unwrap() != "linear" {
55            return None;
56        }
57
58        Some(unsafe { ffi::SCIPgetDualsolLinear(self.scip.raw, self.raw) })
59    }
60
61    /// Returns the Farkas dual solution of the linear constraint in the current (infeasible) LP.
62    /// Returns `None` if the constraint is not a linear constraint.
63    pub fn farkas_dual_sol(&self) -> Option<f64> {
64        let cons_handler = unsafe { ffi::SCIPconsGetHdlr(self.raw) };
65        if cons_handler.is_null() {
66            return None;
67        }
68        let cons_handler_name = unsafe { ffi::SCIPconshdlrGetName(cons_handler) };
69        if cons_handler_name.is_null() {
70            return None;
71        }
72        let cons_handler_name = unsafe { std::ffi::CStr::from_ptr(cons_handler_name) };
73        if cons_handler_name.to_str().unwrap() != "linear" {
74            return None;
75        }
76
77        Some(unsafe { ffi::SCIPgetDualfarkasLinear(self.scip.raw, self.raw) })
78    }
79
80    /// Returns the modifiable flag of the constraint
81    pub fn is_modifiable(&self) -> bool {
82        self.scip.cons_is_modifiable(self)
83    }
84
85    /// Returns the removable flag of the constraint
86    pub fn is_removable(&self) -> bool {
87        self.scip.cons_is_removable(self)
88    }
89
90    /// Returns whether the constraint should be separated during LP processing
91    pub fn is_separated(&self) -> bool {
92        self.scip.cons_is_separated(self)
93    }
94
95    /// Returns the corresponding transformed constraint.
96    /// Returns `None` if the transformed constraint does not exist (yet).
97    pub fn transformed(&self) -> Option<Constraint> {
98        self.scip
99            .get_transformed_cons(self)
100            .ok()
101            .flatten()
102            .map(|raw| Constraint {
103                raw,
104                scip: self.scip.clone(),
105            })
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use crate::{minimal_model, prelude::*};
112    use core::f64;
113
114    #[test]
115    fn test_constraint_mem_safety() {
116        // Create model
117        let mut model = Model::new()
118            .hide_output()
119            .include_default_plugins()
120            .create_prob("test")
121            .set_obj_sense(ObjSense::Maximize);
122
123        let x1 = model.add_var(0., f64::INFINITY, 3., "x1", VarType::Integer);
124        let cons = model.add_cons(vec![&x1], &[1.], 4., 4., "cons");
125        drop(model);
126
127        assert_eq!(cons.name(), "cons");
128    }
129
130    #[test]
131    fn test_constraint_transformed_no_transformed() {
132        let mut model = minimal_model().hide_output().maximize();
133        let x1 = model.add_var(0.0, f64::INFINITY, 10.0, "x1", VarType::Continuous);
134        let cons = model.add_cons(vec![&x1], &[1.0], 0.0, 5.0, "cons");
135
136        assert!(model.solve().best_sol().is_some());
137        assert!(cons.transformed().is_none());
138    }
139
140    #[test]
141    fn test_constraint_transformed_with_transformed() {
142        let mut model = Model::new()
143            .hide_output()
144            .include_default_plugins()
145            .create_prob("prob")
146            .maximize();
147
148        let x1 = model.add_var(0.0, f64::INFINITY, 10.0, "x1", VarType::Continuous);
149        let cons = model.add_cons(vec![&x1], &[1.0], 0.0, 5.0, "cons");
150        model.set_cons_modifiable(&cons, true);
151
152        assert!(model.solve().best_sol().is_some());
153        assert!(cons.transformed().is_some());
154        let dual = cons.transformed().unwrap().dual_sol().unwrap();
155        assert!(dual + 10.0 < f64::EPSILON);
156    }
157}