From 97034213f731130804acbd433e689ec91a234b27 Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Sat, 2 Sep 2023 20:52:22 -0500 Subject: [PATCH 1/4] add support for SCIP cardinality constraints --- src/solvers/scip.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/solvers/scip.rs b/src/solvers/scip.rs index edb968c..87de9ad 100644 --- a/src/solvers/scip.rs +++ b/src/solvers/scip.rs @@ -81,6 +81,23 @@ impl SCIPProblem { pub fn as_inner_mut(&mut self) -> &mut Model { &mut self.model } + + /// Add cardinality constraint. Constrains the number of non-zero variables to at most `rhs`. + pub fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: i32) -> ConstraintReference { + let scip_vars = vars.iter() + .map(|v| Rc::clone(&self.id_for_var[v])) + .collect::>(); + + let index = self.model.n_conss() + 1; + self.model.add_cons_cardinality( + scip_vars, + rhs, + format!("cardinality{}", index).as_str(), + ); + + ConstraintReference { index } + } + } impl SolverModel for SCIPProblem { @@ -194,4 +211,20 @@ mod tests { .unwrap(); assert_eq!((solution.value(x), solution.value(y)), (1., 2.)); } + + #[test] + fn can_solve_cardinality_constraint() { + let mut vars = variables!(); + let x = vars.add(variable().clamp(0, 2).integer()); + let y = vars.add(variable().clamp(0, 3).integer()); + let mut model = vars + .maximise(5.0 * x + 3.0 * y) + .using(scip); + model.add_cardinality_constraint(&[x, y], 1); + let solution = model + .solve() + .unwrap(); + assert_eq!((solution.value(x), solution.value(y)), (2., 0.)); + } + } From d49b7ef890fa7f7d7e86de26f9e079e4bbc45a0e Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Wed, 4 Oct 2023 23:27:08 -0500 Subject: [PATCH 2/4] use usize for cardinality constraint value this makes this version compatible with the code in russcip 0.2.6 --- src/solvers/scip.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solvers/scip.rs b/src/solvers/scip.rs index 87de9ad..7c19960 100644 --- a/src/solvers/scip.rs +++ b/src/solvers/scip.rs @@ -83,7 +83,7 @@ impl SCIPProblem { } /// Add cardinality constraint. Constrains the number of non-zero variables to at most `rhs`. - pub fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: i32) -> ConstraintReference { + pub fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: usize) -> ConstraintReference { let scip_vars = vars.iter() .map(|v| Rc::clone(&self.id_for_var[v])) .collect::>(); From 0b501ec86ed3ebade2dd52d96c95e9857d921994 Mon Sep 17 00:00:00 2001 From: Jonathan Perry Date: Wed, 4 Oct 2023 23:44:59 -0500 Subject: [PATCH 3/4] add trait for solvers that support cardinality constraints this addressess @lovasoa 's comment https://github.com/rust-or/good_lp/pull/39#issuecomment-1704028295 also upgrades russcip dependency to one that supports cardinality constraints. --- Cargo.toml | 2 +- src/cardinality_constraint_solver_trait.rs | 7 +++++++ src/lib.rs | 2 ++ src/solvers/scip.rs | 8 +++++--- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 src/cardinality_constraint_solver_trait.rs diff --git a/Cargo.toml b/Cargo.toml index 7daca34..6da21da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ coin_cbc = { version = "0.1", optional = true, default-features = false } minilp = { version = "0.2", optional = true } lpsolve = { version = "0.1", optional = true } highs = { version = "1.5.0", optional = true } -russcip = { version = "0.2.4", optional = true } +russcip = { version = "0.2.6", optional = true } lp-solvers = { version = "1.0.0", features = ["cplex"], optional = true } fnv = "1.0.5" diff --git a/src/cardinality_constraint_solver_trait.rs b/src/cardinality_constraint_solver_trait.rs new file mode 100644 index 0000000..3134d79 --- /dev/null +++ b/src/cardinality_constraint_solver_trait.rs @@ -0,0 +1,7 @@ +use crate::{Variable, constraint::ConstraintReference}; + +/// A trait for solvers that support cardinality constraints +pub trait CardinalityConstraintSolver { + /// Add cardinality constraint. Constrains the number of non-zero variables from `vars` to at most `rhs`. + fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: usize) -> ConstraintReference; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6f03696..c557207 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,7 @@ //! pub use affine_expression_trait::IntoAffineExpression; +pub use cardinality_constraint_solver_trait::CardinalityConstraintSolver; pub use constraint::Constraint; pub use expression::Expression; #[cfg_attr(docsrs, doc(cfg(feature = "minilp")))] @@ -149,6 +150,7 @@ mod expression; #[macro_use] pub mod variable; mod affine_expression_trait; +mod cardinality_constraint_solver_trait; pub mod constraint; pub mod solvers; mod variables_macro; diff --git a/src/solvers/scip.rs b/src/solvers/scip.rs index 7c19960..e7bc13a 100644 --- a/src/solvers/scip.rs +++ b/src/solvers/scip.rs @@ -15,6 +15,7 @@ use russcip::WithSolutions; use crate::variable::{UnsolvedProblem, VariableDefinition}; use crate::{ + CardinalityConstraintSolver, constraint::ConstraintReference, solvers::{ObjectiveDirection, ResolutionError, Solution, SolverModel}, }; @@ -81,9 +82,11 @@ impl SCIPProblem { pub fn as_inner_mut(&mut self) -> &mut Model { &mut self.model } +} +impl CardinalityConstraintSolver for SCIPProblem { /// Add cardinality constraint. Constrains the number of non-zero variables to at most `rhs`. - pub fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: usize) -> ConstraintReference { + fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: usize) -> ConstraintReference { let scip_vars = vars.iter() .map(|v| Rc::clone(&self.id_for_var[v])) .collect::>(); @@ -97,7 +100,6 @@ impl SCIPProblem { ConstraintReference { index } } - } impl SolverModel for SCIPProblem { @@ -179,7 +181,7 @@ impl Solution for SCIPSolved { #[cfg(test)] mod tests { - use crate::{constraint, variable, variables, Solution, SolverModel}; + use crate::{constraint, variable, variables, Solution, SolverModel, CardinalityConstraintSolver}; use super::scip; From ddf7aed7033ca6723d74444695484fc29ffb89a4 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Thu, 5 Oct 2023 10:37:44 +0200 Subject: [PATCH 4/4] cargo fmt --- src/cardinality_constraint_solver_trait.rs | 4 ++-- src/solvers/scip.rs | 25 +++++++++------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/cardinality_constraint_solver_trait.rs b/src/cardinality_constraint_solver_trait.rs index 3134d79..07b0223 100644 --- a/src/cardinality_constraint_solver_trait.rs +++ b/src/cardinality_constraint_solver_trait.rs @@ -1,7 +1,7 @@ -use crate::{Variable, constraint::ConstraintReference}; +use crate::{constraint::ConstraintReference, Variable}; /// A trait for solvers that support cardinality constraints pub trait CardinalityConstraintSolver { /// Add cardinality constraint. Constrains the number of non-zero variables from `vars` to at most `rhs`. fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: usize) -> ConstraintReference; -} \ No newline at end of file +} diff --git a/src/solvers/scip.rs b/src/solvers/scip.rs index e7bc13a..742325e 100644 --- a/src/solvers/scip.rs +++ b/src/solvers/scip.rs @@ -15,9 +15,9 @@ use russcip::WithSolutions; use crate::variable::{UnsolvedProblem, VariableDefinition}; use crate::{ - CardinalityConstraintSolver, constraint::ConstraintReference, solvers::{ObjectiveDirection, ResolutionError, Solution, SolverModel}, + CardinalityConstraintSolver, }; use crate::{Constraint, Variable}; @@ -87,16 +87,14 @@ impl SCIPProblem { impl CardinalityConstraintSolver for SCIPProblem { /// Add cardinality constraint. Constrains the number of non-zero variables to at most `rhs`. fn add_cardinality_constraint(&mut self, vars: &[Variable], rhs: usize) -> ConstraintReference { - let scip_vars = vars.iter() + let scip_vars = vars + .iter() .map(|v| Rc::clone(&self.id_for_var[v])) .collect::>(); let index = self.model.n_conss() + 1; - self.model.add_cons_cardinality( - scip_vars, - rhs, - format!("cardinality{}", index).as_str(), - ); + self.model + .add_cons_cardinality(scip_vars, rhs, format!("cardinality{}", index).as_str()); ConstraintReference { index } } @@ -181,7 +179,9 @@ impl Solution for SCIPSolved { #[cfg(test)] mod tests { - use crate::{constraint, variable, variables, Solution, SolverModel, CardinalityConstraintSolver}; + use crate::{ + constraint, variable, variables, CardinalityConstraintSolver, Solution, SolverModel, + }; use super::scip; @@ -219,14 +219,9 @@ mod tests { let mut vars = variables!(); let x = vars.add(variable().clamp(0, 2).integer()); let y = vars.add(variable().clamp(0, 3).integer()); - let mut model = vars - .maximise(5.0 * x + 3.0 * y) - .using(scip); + let mut model = vars.maximise(5.0 * x + 3.0 * y).using(scip); model.add_cardinality_constraint(&[x, y], 1); - let solution = model - .solve() - .unwrap(); + let solution = model.solve().unwrap(); assert_eq!((solution.value(x), solution.value(y)), (2., 0.)); } - }