From 08fc3039e3dcc96eb370da1ad7f61d096d5ff06e Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Thu, 9 Jan 2025 16:37:49 +0100 Subject: [PATCH 1/3] Add method to add cuts --- src/model.rs | 16 ++++++++++++++- src/row.rs | 7 ++++++- src/scip.rs | 12 ++++++----- src/separator.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/model.rs b/src/model.rs index d137995..5bfb368 100644 --- a/src/model.rs +++ b/src/model.rs @@ -8,7 +8,7 @@ use crate::scip::ScipPtr; use crate::solution::{SolError, Solution}; use crate::status::Status; use crate::variable::{VarId, VarType, Variable}; -use crate::{ffi, Separator}; +use crate::{ffi, Row, Separator}; use crate::{BranchRule, HeurTiming, Heuristic, Pricer}; /// Represents an optimization model. @@ -470,6 +470,20 @@ impl Model { }) } } + + /// Adds a new cut (row) to the model. + /// + /// # Arguments + /// * `row` - The row to add. + /// * `force_cut` - If true, the cut (row) is forced to be selected. + /// + /// # Returns + /// A boolean indicating whether the row is infeasible from the local bounds. + pub fn add_cut(&mut self, cut: Row, force_cut: bool) -> bool { + self.scip() + .add_row(cut, force_cut) + .expect("Failed to add row in state ProblemCreated") + } } impl Model { diff --git a/src/row.rs b/src/row.rs index cb0c660..b3d7865 100644 --- a/src/row.rs +++ b/src/row.rs @@ -1,5 +1,5 @@ use crate::scip::ScipPtr; -use crate::{ffi, Constraint}; +use crate::{ffi, Constraint, Variable}; use std::ffi::c_int; use std::rc::Rc; @@ -162,6 +162,11 @@ impl Row { pub fn set_rank(&self, rank: usize) { unsafe { ffi::SCIProwChgRank(self.raw, rank as c_int) }; } + + /// Sets the coefficient of a variable in the row. + pub fn set_coeff(&self, var: &Variable, coeff: f64) { + unsafe { ffi::SCIPaddVarToRow(self.scip.raw, self.raw, var.raw, coeff) }; + } } impl PartialEq for Row { diff --git a/src/scip.rs b/src/scip.rs index 0301a04..a8cefc6 100644 --- a/src/scip.rs +++ b/src/scip.rs @@ -1,10 +1,6 @@ use crate::branchrule::{BranchRule, BranchingCandidate}; use crate::pricer::{Pricer, PricerResultState}; -use crate::{ - ffi, scip_call_panic, BranchingResult, Constraint, Event, Eventhdlr, HeurResult, Model, Node, - ObjSense, ParamSetting, Retcode, SCIPEventhdlr, SCIPPricer, SCIPSeparator, Separator, Solution, - Solving, Status, VarType, Variable, -}; +use crate::{ffi, scip_call_panic, BranchingResult, Constraint, Event, Eventhdlr, HeurResult, Model, Node, ObjSense, ParamSetting, Retcode, Row, SCIPEventhdlr, SCIPPricer, SCIPSeparator, Separator, Solution, Solving, Status, VarType, Variable}; use crate::{scip_call, HeurTiming, Heuristic}; use core::panic; use scip_sys::{SCIP_Cons, SCIP_Var, Scip, SCIP_SOL}; @@ -1145,6 +1141,12 @@ impl ScipPtr { scip_call!(ffi::SCIPfreeTransform(self.raw)); Ok(()) } + + pub(crate) fn add_row(&self, row: Row, force_cut: bool) -> Result { + let mut infeasible = 0; + scip_call!(ffi::SCIPaddRow(self.raw, row.raw, force_cut.into(), &mut infeasible)); + Ok(infeasible != 0) + } } impl Drop for ScipPtr { diff --git a/src/separator.rs b/src/separator.rs index 99dcee0..9573360 100644 --- a/src/separator.rs +++ b/src/separator.rs @@ -117,7 +117,7 @@ impl SCIPSeparator { /// Creates an empty LP row. pub fn create_empty_row( &self, - model: Model, + model: &Model, name: &str, lhs: f64, rhs: f64, @@ -238,7 +238,7 @@ mod tests { assert_eq!(sep.maxbounddist(), 1.0); assert!(!sep.is_delayed()); let row = sep - .create_empty_row(model, "test", 0.0, 1.0, true, false, false) + .create_empty_row(&model, "test", 0.0, 1.0, true, false, false) .unwrap(); assert_eq!(row.name(), "test"); assert_eq!(row.lhs(), 0.0); @@ -277,4 +277,52 @@ mod tests { ) .solve(); } + + struct CutsAddingSeparator; + + impl Separator for CutsAddingSeparator { + fn execute_lp( + &mut self, + mut model: Model, + sepa: SCIPSeparator, + ) -> SeparationResult { + // adds a row representing the sum of all variables == 5, causing infeasibility + let row = sepa.create_empty_row(&model, "test", 5.0, 5.0, true, false, false).unwrap(); + for var in model.vars() { + row.set_coeff(&var, 1.0); + } + model.add_row(row, true); + + SeparationResult::Separated + } + } + + #[test] + fn cuts_adding() { + let mut model = minimal_model() + .hide_output() + .set_obj_sense(ObjSense::Maximize); + + let x = model.add_var(0.0, 1.0, 1.0, "x", VarType::Binary); + let y = model.add_var(0.0, 1.0, 1.0, "y", VarType::Binary); + + model.add_cons(vec![x, y], &[1.0, 1.0], 1.0, 1.0, "cons1"); + + let sep = CutsAddingSeparator {}; + + let solved = model + .include_separator( + "CutsAddingSeparator", + "", + 1000000, + 1, + 1.0, + false, + false, + Box::new(sep), + ) + .solve(); + + assert_eq!(solved.status(), crate::Status::Infeasible); + } } From bb40664aea91be60442c260bfef8003a5ac8c3dd Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Thu, 9 Jan 2025 16:38:22 +0100 Subject: [PATCH 2/3] Fix name --- src/separator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/separator.rs b/src/separator.rs index 9573360..9805965 100644 --- a/src/separator.rs +++ b/src/separator.rs @@ -291,7 +291,7 @@ mod tests { for var in model.vars() { row.set_coeff(&var, 1.0); } - model.add_row(row, true); + model.add_cut(row, true); SeparationResult::Separated } From 6d10daabe21a01a7fc1eee1a1f0f4e5b287dd6bc Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Thu, 9 Jan 2025 16:38:39 +0100 Subject: [PATCH 3/3] Cargo fmt --- src/scip.rs | 13 +++++++++++-- src/separator.rs | 4 +++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/scip.rs b/src/scip.rs index a8cefc6..6317f5c 100644 --- a/src/scip.rs +++ b/src/scip.rs @@ -1,6 +1,10 @@ use crate::branchrule::{BranchRule, BranchingCandidate}; use crate::pricer::{Pricer, PricerResultState}; -use crate::{ffi, scip_call_panic, BranchingResult, Constraint, Event, Eventhdlr, HeurResult, Model, Node, ObjSense, ParamSetting, Retcode, Row, SCIPEventhdlr, SCIPPricer, SCIPSeparator, Separator, Solution, Solving, Status, VarType, Variable}; +use crate::{ + ffi, scip_call_panic, BranchingResult, Constraint, Event, Eventhdlr, HeurResult, Model, Node, + ObjSense, ParamSetting, Retcode, Row, SCIPEventhdlr, SCIPPricer, SCIPSeparator, Separator, + Solution, Solving, Status, VarType, Variable, +}; use crate::{scip_call, HeurTiming, Heuristic}; use core::panic; use scip_sys::{SCIP_Cons, SCIP_Var, Scip, SCIP_SOL}; @@ -1144,7 +1148,12 @@ impl ScipPtr { pub(crate) fn add_row(&self, row: Row, force_cut: bool) -> Result { let mut infeasible = 0; - scip_call!(ffi::SCIPaddRow(self.raw, row.raw, force_cut.into(), &mut infeasible)); + scip_call!(ffi::SCIPaddRow( + self.raw, + row.raw, + force_cut.into(), + &mut infeasible + )); Ok(infeasible != 0) } } diff --git a/src/separator.rs b/src/separator.rs index 9805965..cad8409 100644 --- a/src/separator.rs +++ b/src/separator.rs @@ -287,7 +287,9 @@ mod tests { sepa: SCIPSeparator, ) -> SeparationResult { // adds a row representing the sum of all variables == 5, causing infeasibility - let row = sepa.create_empty_row(&model, "test", 5.0, 5.0, true, false, false).unwrap(); + let row = sepa + .create_empty_row(&model, "test", 5.0, 5.0, true, false, false) + .unwrap(); for var in model.vars() { row.set_coeff(&var, 1.0); }