From bad98aa7832356b7172230c70b746a522b2ab914 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 10 Jan 2024 13:31:44 -0500 Subject: [PATCH 1/2] Add indicator constraints --- CHANGELOG.md | 1 + src/model.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/scip.rs | 30 ++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa418bc..f5bc3d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## untracked ### Added + - Add support for indicator constraints ### Fixed - Fixed Windows MSVC build. ### Changed diff --git a/src/model.rs b/src/model.rs index 10e6a38..c0242a8 100644 --- a/src/model.rs +++ b/src/model.rs @@ -677,6 +677,32 @@ pub trait ProblemOrSolving { cardinality: usize, name: &str, ) -> Rc; + + /// Adds a new indicator constraint to the model with the given variables, coefficients, right-hand side, and name. + /// + /// # Arguments + /// + /// * `bin_var` - The binary variable in the constraint. + /// * `vars` - The variables of the constraints. + /// * `coefs` - The coefficients of the variables in the constraint. + /// * `rhs` - The right-hand side of the constraint. + /// * `name` - The name of the constraint. + /// + /// # Returns + /// + /// A reference-counted pointer to the new constraint. + /// + /// # Panics + /// + /// This method panics if the constraint cannot be created in the current state. + fn add_cons_indicator( + &mut self, + bin_var: Rc, + vars: Vec>, + coefs: &mut [f64], + rhs: f64, + name: &str, + ) -> Rc; } macro_rules! impl_ProblemOrSolving { @@ -927,6 +953,40 @@ macro_rules! impl_ProblemOrSolving { cons } + /// Adds a new indicator constraint to the model with the given variables, coefficients, right-hand side, and name. + /// + /// # Arguments + /// + /// * `bin_var` - The binary variable in the constraint. + /// * `vars` - The variables of the constraints. + /// * `coefs` - The coefficients of the variables in the constraint. + /// * `rhs` - The right-hand side of the constraint. + /// * `name` - The name of the constraint. + /// + /// # Returns + /// + /// A reference-counted pointer to the new constraint. + /// + /// # Panics + /// + /// This method panics if the constraint cannot be created in the current state. + fn add_cons_indicator( + &mut self, + bin_var: Rc, + vars: Vec>, + coefs: &mut [f64], + rhs: f64, + name: &str, + ) -> Rc { + assert_eq!(vars.len(), coefs.len()); + let cons = self + .scip + .create_cons_indicator(bin_var, vars, coefs, rhs, name) + .expect("Failed to create constraint in state ProblemCreated"); + let cons = Rc::new(cons); + self.state.conss.borrow_mut().push(cons.clone()); + cons + } })* } } @@ -1449,6 +1509,45 @@ mod tests { assert_eq!(solution.val(x3), 10.); } + #[test] + fn indicator_constraint() { + let mut model = Model::new() + .hide_output() + .include_default_plugins() + .create_prob("test") + .set_obj_sense(ObjSense::Maximize); + + // set up two integers variables with weight 1 and a binary variable with weight 0 + let x1 = model.add_var(0., 10., 1., "x1", VarType::Integer); + let x2 = model.add_var(0., 10., 1., "x2", VarType::Integer); + let b = model.add_var(0., 1., 0., "b", VarType::Binary); + + // Indicator constraint: `b == 1` implies `x1 - x2 <= -1` + model.add_cons_indicator( + b.clone(), + vec![x1.clone(), x2.clone()], + &mut [1., -1.], + -1., + "indicator" + ); + + // Force `b` to be exactly 1 and later make sure that the constraint `x1 - x2 <= -1` is + // indeed active + model.add_cons(vec![b.clone()], &[1.], 1., 1., "c1"); + + let solved_model = model.solve(); + let status = solved_model.status(); + assert_eq!(status, Status::Optimal); + assert_eq!(solved_model.obj_val(), 19.); + + let solution = solved_model.best_sol().unwrap(); + + // Indeed `x1 - x2 <= -1` when `b == 1` + assert_eq!(solution.val(x1), 9.); + assert_eq!(solution.val(x2), 10.); + assert_eq!(solution.val(b), 1.); + } + #[test] fn create_sol() { let mut model = Model::new() diff --git a/src/scip.rs b/src/scip.rs index e11e5fb..8c30246 100644 --- a/src/scip.rs +++ b/src/scip.rs @@ -453,6 +453,36 @@ impl ScipPtr { Ok(Constraint { raw: scip_cons }) } + pub(crate) fn create_cons_indicator( + &mut self, + bin_var: Rc, + vars: Vec>, + coefs: &mut [f64], + rhs: f64, + name: &str, + ) -> Result { + assert_eq!(vars.len(), coefs.len()); + let c_name = CString::new(name).unwrap(); + let mut scip_cons = MaybeUninit::uninit(); + + scip_call! { ffi::SCIPcreateConsBasicIndicator( + self.raw, + scip_cons.as_mut_ptr(), + c_name.as_ptr(), + bin_var.raw, + vars.len() as c_int, + (vars.into_iter() + .map(|var_rc| var_rc.raw) + .collect::>()).as_mut_ptr(), + coefs.as_mut_ptr(), + rhs, + ) }; + + let scip_cons = unsafe { scip_cons.assume_init() }; + scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; + Ok(Constraint { raw: scip_cons }) + } + /// Create solution pub(crate) fn create_sol(&self) -> Result { let mut sol = MaybeUninit::uninit(); From feb56fbb11882fa5e46aec103db09b72fc1717e4 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Thu, 11 Jan 2024 09:10:46 -0500 Subject: [PATCH 2/2] Assert that bin_var is indeed a Binary variable --- src/model.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/model.rs b/src/model.rs index c0242a8..977a2ab 100644 --- a/src/model.rs +++ b/src/model.rs @@ -979,6 +979,7 @@ macro_rules! impl_ProblemOrSolving { name: &str, ) -> Rc { assert_eq!(vars.len(), coefs.len()); + assert_eq!(bin_var.var_type(), VarType::Binary); let cons = self .scip .create_cons_indicator(bin_var, vars, coefs, rhs, name)