1use crate::scip::ScipPtr;
2use crate::{Row, ffi};
3use std::rc::Rc;
4
5#[derive(Debug, Clone)]
7#[allow(dead_code)]
8pub struct Constraint {
9 pub(crate) raw: *mut ffi::SCIP_CONS,
11 pub(crate) scip: Rc<ScipPtr>,
13}
14
15impl Constraint {
16 pub fn inner(&self) -> *mut ffi::SCIP_CONS {
18 self.raw
19 }
20
21 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 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 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 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 pub fn is_modifiable(&self) -> bool {
82 self.scip.cons_is_modifiable(self)
83 }
84
85 pub fn is_removable(&self) -> bool {
87 self.scip.cons_is_removable(self)
88 }
89
90 pub fn is_separated(&self) -> bool {
92 self.scip.cons_is_separated(self)
93 }
94
95 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 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}