From 2e499f8899c85c378d906b26ed3c37c1438bc954 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Sun, 25 Jun 2023 10:36:28 +0200 Subject: [PATCH 1/6] Test solution printing --- src/solution.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/solution.rs b/src/solution.rs index b380f26..389c9f0 100644 --- a/src/solution.rs +++ b/src/solution.rs @@ -54,3 +54,36 @@ pub enum SolError { /// The solution is infeasible. Infeasible } + + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn sol_methods() { + let mut model = Model::new() + .hide_output() + .include_default_plugins() + .read_prob("data/test/simple.lp") + .unwrap() + .solve(); + + let status = model.status(); + assert_eq!(status, Status::Optimal); + + //test solution values + let sol = model.best_sol().unwrap(); + + let debug_str = format!("{:?}", sol); + assert!(debug_str.contains("Solution with obj val: 200")); + assert!(debug_str.contains("Var t_x1=40")); + assert!(debug_str.contains("Var t_x2=20")); + + let vars = model.vars(); + assert_eq!(sol.val(vars[0].clone()), 40.); + assert_eq!(sol.val(vars[1].clone()), 20.); + + assert_eq!(sol.obj_val(), model.obj_val()); + } +} \ No newline at end of file From f91bbb4fd68cf6e9f36fc6e5dc4beab12f329412 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Sun, 25 Jun 2023 10:38:49 +0200 Subject: [PATCH 2/6] Update version number --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f146cf5..5d89f7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ authors = ["Mohammad Ghannam "] description = "Rust interface for SCIP" license = "Apache-2.0" repository = "https://github.com/scipopt/russcip" -version = "0.2.3" +version = "0.2.4" edition = "2021" exclude = ["data/test/*"] From 2ea1f613f98e13e5fbdaacb8dfa10fd5ba71691a Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Sun, 25 Jun 2023 10:40:14 +0200 Subject: [PATCH 3/6] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bccaef1..66b922a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## unreleased ### Added +### Fixed +### Changed +### Removed + +## 0.2.4 +### Added - Model methods to create child from the current focus node. - Node method to get its parent. - Methods to add set cover and set packing constraints. From d2317158586feee4eaaa44d591b82515b0742690 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Thu, 27 Jul 2023 10:19:39 +0200 Subject: [PATCH 4/6] Move ScipPtr struct and methods to its own module (#96) * Update version number (#84) * Test solution printing * Update version number * Update CHANGELOG * Primal heuristic plugin (#85) * Add primal heuristic plugin * Add primal heuristic plugin * Update CHANGELOG * Raise lowest coverage limit (#86) * Fix links * Rename back method name * Use correct file name * CI: use download links from github (#87) * CI: use download links from github * Use ubuntu download link * Generate correct download link from version * Change status of project in README (#94) * Move scipptr struct and methods to its own module * Update changelog * Make raw scip ptr visible to crate --- .codecov.yml | 4 +- .github/workflows/build_and_test.yml | 4 +- CHANGELOG.md | 2 + README.md | 3 +- src/eventhdlr.rs | 4 +- src/heuristic.rs | 280 ++++++++ src/lib.rs | 6 + src/model.rs | 908 ++------------------------ src/scip.rs | 931 +++++++++++++++++++++++++++ src/solution.rs | 9 +- 10 files changed, 1291 insertions(+), 860 deletions(-) create mode 100644 src/heuristic.rs create mode 100644 src/scip.rs diff --git a/.codecov.yml b/.codecov.yml index 16ea9d2..9f65bd8 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,4 +1,4 @@ coverage: - range: 70..100 + range: 90..100 round: nearest - precision: 2 \ No newline at end of file + precision: 2 diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 0ec2bcc..b8613ee 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v2 - name: Install dependencies (SCIPOptSuite) run: | - wget --quiet --no-check-certificate https://scipopt.org/download/release/SCIPOptSuite-${{ env.version }}-Linux-ubuntu.deb + wget --quiet --no-check-certificate https://github.com/scipopt/scip/releases/download/$(echo "v${{env.version}}" | tr -d '.')/SCIPOptSuite-${{ env.version }}-Linux-ubuntu.deb sudo apt-get update && sudo apt install -y ./SCIPOptSuite-${{ env.version }}-Linux-ubuntu.deb - name: Install tarpaulin @@ -124,4 +124,4 @@ jobs: # export SCIPOPTDIR=${{ runner.workspace }}/scipoptsuite/ # export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:${{ runner.workspace }}/scipoptsuite/lib # cargo build -# cargo test \ No newline at end of file +# cargo test diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b922a..4e8f1ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ## unreleased ### Added +- Primal heuristic plugin ### Fixed ### Changed +- Moved ScipPtr struct and methods to its own module. ### Removed ## 0.2.4 diff --git a/README.md b/README.md index 82d9266..89dd4e2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [img_coverage]: https://img.shields.io/codecov/c/github/scipopt/russcip A safe Rust interface for [SCIP](https://www.scipopt.org/index.php#download). This crate also exposes access to the SCIP's C-API through the `ffi` module. -The project is currently an early-stage work in progress, issues/pull-requests are very welcome. +The project is currently actively developed, issues/pull-requests are very welcome. ## Dependencies Make sure SCIP is installed, the easiest way to install it is to install a precompiled package from [here](https://scipopt.org/index.php#download) or through conda by running ```bash @@ -95,6 +95,7 @@ Some of SCIP's plugins are imported to the rust interface as traits. Currently t | Branching rule| [branchrule.rs](https://github.com/scipopt/russcip/blob/main/src/branchrule.rs) | [docs](https://docs.rs/russcip/latest/russcip/branchrule/trait.BranchRule.html) | | Variable Pricer| [pricer.rs](https://github.com/scipopt/russcip/blob/main/src/pricer.rs) | [docs](https://docs.rs/russcip/latest/russcip/pricer/trait.Pricer.html) | | Event Handler | [eventhdlr.rs](https://github.com/scipopt/russcip/blob/main/src/eventhdlr.rs) | [docs](https://docs.rs/russcip/latest/russcip/eventhdlr/trait.Eventhdlr.html) | +| Primal Heuristic | [heuristic.rs](https://github.com/scipopt/russcip/blob/main/src/heuristic.rs) | [docs](https://docs.rs/russcip/latest/russcip/eventhdlr/trait.Heuristic.html) | To add a custom plugin to a SCIP `Model` instance, you should implement its trait and call the corresponding `include_{PLUGIN_NAME}` method. For examples on implementing the specific plugin trait you can check the tests in the corresponding files. diff --git a/src/eventhdlr.rs b/src/eventhdlr.rs index 040dc1e..29fa4d9 100644 --- a/src/eventhdlr.rs +++ b/src/eventhdlr.rs @@ -3,7 +3,7 @@ use std::ops::{BitOr, BitOrAssign}; /// Trait used to define custom event handlers. pub trait Eventhdlr { /// Returns the type of the event handler. - fn var_type(&self) -> EventMask; + fn get_type(&self) -> EventMask; /// Executes the event handler. fn execute(&mut self); } @@ -176,7 +176,7 @@ mod tests { } impl Eventhdlr for CountingEventHdlr { - fn var_type(&self) -> EventMask { + fn get_type(&self) -> EventMask { EventMask::LP_EVENT | EventMask::NODE_EVENT } diff --git a/src/heuristic.rs b/src/heuristic.rs new file mode 100644 index 0000000..6fe3275 --- /dev/null +++ b/src/heuristic.rs @@ -0,0 +1,280 @@ +use std::ops::{BitOr, BitOrAssign}; + +use crate::ffi; +use crate::Solution; + +/// A trait for defining custom primal heuristics. +pub trait Heuristic { + /// Executes the heuristic. + /// + /// # Arguments + /// * `timing` - the timing mask of the heuristic's execution + /// * `node_inf` - whether the current node is infeasible + /// + /// # Returns + /// + /// * `HeurResult::FoundSol` if a new incumbent solution was found + /// * `HeurResult::NoSolFound` if no new incumbent solution was found + /// * `HeurResult::DidNotRun` if the heuristic was not executed + /// * `HeurResult::Delayed` if the heuristic is delayed (skipped but should be called again) + fn execute(&mut self, timing: HeurTiming, node_inf: bool) -> HeurResult; +} + +/// The result of a primal heuristic execution. +#[derive(Debug, PartialEq, Eq)] +pub enum HeurResult { + /// The heuristic found a new incumbent solution. + FoundSol, + /// The heuristic did not find a new solution. + NoSolFound, + /// The heuristic was not executed. + DidNotRun, + /// The heuristic is delayed (skipped but should be called again). + Delayed, +} + +/// The Heur represents different timing masks for the execution of a heuristic. +#[derive(Debug, Copy, Clone)] +pub struct HeurTiming(u64); + +impl HeurTiming { + /// call heuristic before the processing of the node starts + pub const BEFORE_NODE: Self = HeurTiming(0x001); + /// call heuristic after each LP solving during cut-and-price loop + pub const DURING_LP_LOOP: Self = HeurTiming(0x002); + /// call heuristic after the cut-and-price loop was finished + pub const AFTER_LP_LOOP: Self = HeurTiming(0x004); + /// call heuristic after the processing of a node with solved LP was finished + pub const AFTER_LP_NODE: Self = HeurTiming(0x008); + /// call heuristic after the processing of a node without solved LP was finished + pub const AFTER_PSEUDO_NODE: Self = HeurTiming(0x010); + /// call heuristic after the processing of the last node in the current plunge was finished, and only if the LP was solved for this node + pub const AFTER_LP_PLUNGE: Self = HeurTiming(0x020); + /// call heuristic after the processing of the last node in the current plunge was finished, and only if the LP was not solved for this node + pub const AFTER_PSEUDO_PLUNGE: Self = HeurTiming(0x040); + /// call heuristic during pricing loop + pub const DURING_PRICING_LOOP: Self = HeurTiming(0x080); + /// call heuristic before presolving + pub const BEFORE_PRESOL: Self = HeurTiming(0x100); + /// call heuristic during presolving loop + pub const DURING_PRESOL_LOOP: Self = HeurTiming(0x200); + /// call heuristic after propagation which is performed before solving the LP + pub const AFTER_PROP_LOOP: Self = HeurTiming(0x400); +} + +impl BitOr for HeurTiming { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + HeurTiming(self.0 | rhs.0) + } +} + +impl BitOrAssign for HeurTiming { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +impl From for u32 { + fn from(mask: HeurTiming) -> Self { + mask.0 as u32 + } +} + +impl From for HeurTiming { + fn from(mask: u32) -> Self { + HeurTiming(mask as u64) + } +} + +impl From for u32 { + fn from(val: HeurResult) -> Self { + match val { + HeurResult::FoundSol => ffi::SCIP_Result_SCIP_FOUNDSOL, + HeurResult::NoSolFound => ffi::SCIP_Result_SCIP_DIDNOTFIND, + HeurResult::DidNotRun => ffi::SCIP_Result_SCIP_DIDNOTRUN, + HeurResult::Delayed => ffi::SCIP_Result_SCIP_DELAYED, + } + } +} + + +#[cfg(test)] +mod tests { + use crate::{Model, ModelWithProblem, ProblemCreated, SolError}; + + use super::*; + + struct NoSolutionFoundHeur; + + impl Heuristic for NoSolutionFoundHeur { + fn execute(&mut self, _timing: HeurTiming, _node_inf: bool) -> HeurResult { + HeurResult::NoSolFound + } + } + + #[test] + fn test_heur() { + let model = Model::new() + .hide_output() + .include_default_plugins() + .read_prob("data/test/simple.lp") + .unwrap(); + + let heur = NoSolutionFoundHeur; + let mut timing = HeurTiming::BEFORE_PRESOL; + timing |= HeurTiming::AFTER_PROP_LOOP; + model.include_heur( + "no_sol_found_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + timing, + false, + Box::new(heur), + ).solve(); + + } + + struct ImpostorHeur; + + impl Heuristic for ImpostorHeur { + fn execute(&mut self, _timing: HeurTiming, _node_inf: bool) -> HeurResult { + HeurResult::FoundSol + } + } + + #[test] + #[should_panic] + fn impostor_heur() { + let model = Model::new() + .hide_output() + .include_default_plugins() + .read_prob("data/test/simple.lp") + .unwrap(); + + let heur = ImpostorHeur; + model.include_heur( + "impostor_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE | HeurTiming::AFTER_LP_NODE, + false, + Box::new(heur), + ).solve(); + } + + + struct DelayedHeur; + + impl Heuristic for DelayedHeur { + fn execute(&mut self, _timing: HeurTiming, _node_inf: bool) -> HeurResult { + HeurResult::Delayed + } + } + + + #[test] + fn delayed_heur() { + let model = Model::new() + .hide_output() + .include_default_plugins() + .read_prob("data/test/simple.lp") + .unwrap(); + + let heur = DelayedHeur; + model.include_heur( + "delayed_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE, + false, + Box::new(heur), + ).solve(); + } + + + struct DidNotRunHeur; + + impl Heuristic for DidNotRunHeur { + fn execute(&mut self, _timing: HeurTiming, _node_inf: bool) -> HeurResult { + HeurResult::DidNotRun + } + } + + #[test] + fn did_not_run_heur() { + let model = Model::new() + .hide_output() + .include_default_plugins() + .read_prob("data/test/simple.lp") + .unwrap(); + + let heur = DidNotRunHeur; + model.include_heur( + "did_not_run_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE, + false, + Box::new(heur), + ).solve(); + } + + + struct FoundSolHeur { + model: Model, + } + + impl Heuristic for FoundSolHeur { + fn execute(&mut self, _timing: HeurTiming, _node_inf: bool) -> HeurResult { + let sol = self.model.create_sol(); + for var in self.model.vars() { + sol.set_val(var, 1.0); + } + assert_eq!(sol.obj_val(), 7.0); + assert_eq!(self.model.add_sol(sol), Ok(())); + HeurResult::FoundSol + } + } + + + #[test] + fn found_sol_heur() { + let model = Model::new() + .hide_output() + .include_default_plugins() + .read_prob("data/test/simple.lp") + .unwrap(); + + let heur = FoundSolHeur { model: model.clone_for_plugins() }; + model.include_heur( + "found_sol_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE, + false, + Box::new(heur), + ).solve(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1ab1949..d1b04b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,12 @@ pub use node::*; pub mod eventhdlr; pub use eventhdlr::*; +/// Contains the `Heur` trait used to define custom primal heuristics. +pub mod heuristic; +pub use heuristic::*; + +mod scip; + /// A macro for calling a `SCIP` function and returning an error if the return code is not `SCIP_OKAY`. #[macro_export] macro_rules! scip_call { diff --git a/src/model.rs b/src/model.rs index 9a7fe56..d910663 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,857 +1,18 @@ -use core::panic; use std::cell::RefCell; use std::collections::BTreeMap; -use std::ffi::{c_int, CString}; -use std::mem::MaybeUninit; + use std::rc::Rc; -use crate::branchrule::{BranchRule, BranchingCandidate, BranchingResult}; use crate::constraint::Constraint; use crate::eventhdlr::Eventhdlr; +use crate::ffi; use crate::node::Node; -use crate::pricer::{Pricer, PricerResultState}; use crate::retcode::Retcode; -use crate::scip_call; +use crate::scip::ScipPtr; use crate::solution::{SolError, Solution}; use crate::status::Status; use crate::variable::{VarId, VarType, Variable}; -use crate::{ffi, scip_call_panic}; - -#[non_exhaustive] -#[derive(Debug)] -struct ScipPtr { - raw: *mut ffi::SCIP, - consumed: bool, -} - -impl ScipPtr { - fn new() -> Self { - let mut scip_ptr = MaybeUninit::uninit(); - scip_call_panic!(ffi::SCIPcreate(scip_ptr.as_mut_ptr())); - let scip_ptr = unsafe { scip_ptr.assume_init() }; - ScipPtr { - raw: scip_ptr, - consumed: false, - } - } - - fn clone(&self) -> Self { - ScipPtr { - raw: self.raw, - consumed: true, - } - } - - fn set_str_param(&mut self, param: &str, value: &str) -> Result<(), Retcode> { - let param = CString::new(param).unwrap(); - let value = CString::new(value).unwrap(); - scip_call! { ffi::SCIPsetStringParam(self.raw, param.as_ptr(), value.as_ptr()) }; - Ok(()) - } - - fn set_int_param(&mut self, param: &str, value: i32) -> Result<(), Retcode> { - let param = CString::new(param).unwrap(); - scip_call! { ffi::SCIPsetIntParam(self.raw, param.as_ptr(), value) }; - Ok(()) - } - - fn set_longint_param(&mut self, param: &str, value: i64) -> Result<(), Retcode> { - let param = CString::new(param).unwrap(); - scip_call! { ffi::SCIPsetLongintParam(self.raw, param.as_ptr(), value) }; - Ok(()) - } - - fn set_real_param(&mut self, param: &str, value: f64) -> Result<(), Retcode> { - let param = CString::new(param).unwrap(); - scip_call! { ffi::SCIPsetRealParam(self.raw, param.as_ptr(), value) }; - Ok(()) - } - - fn set_presolving(&mut self, presolving: ParamSetting) -> Result<(), Retcode> { - scip_call! { ffi::SCIPsetPresolving(self.raw, presolving.into(), true.into()) }; - Ok(()) - } - - fn set_separating(&mut self, separating: ParamSetting) -> Result<(), Retcode> { - scip_call! { ffi::SCIPsetSeparating(self.raw, separating.into(), true.into()) }; - Ok(()) - } - - fn set_heuristics(&mut self, heuristics: ParamSetting) -> Result<(), Retcode> { - scip_call! { ffi::SCIPsetHeuristics(self.raw, heuristics.into(), true.into()) }; - Ok(()) - } - - fn create_prob(&mut self, name: &str) -> Result<(), Retcode> { - let name = CString::new(name).unwrap(); - scip_call!(ffi::SCIPcreateProbBasic(self.raw, name.as_ptr())); - Ok(()) - } - - fn read_prob(&mut self, filename: &str) -> Result<(), Retcode> { - let filename = CString::new(filename).unwrap(); - scip_call!(ffi::SCIPreadProb( - self.raw, - filename.as_ptr(), - std::ptr::null_mut() - )); - Ok(()) - } - - fn set_obj_sense(&mut self, sense: ObjSense) -> Result<(), Retcode> { - scip_call!(ffi::SCIPsetObjsense(self.raw, sense.into())); - Ok(()) - } - - fn n_vars(&self) -> usize { - unsafe { ffi::SCIPgetNVars(self.raw) as usize } - } - - fn n_conss(&self) -> usize { - unsafe { ffi::SCIPgetNConss(self.raw) as usize } - } - - fn status(&self) -> Status { - let status = unsafe { ffi::SCIPgetStatus(self.raw) }; - status.try_into().expect("Unknown SCIP status") - } - - fn print_version(&self) { - unsafe { ffi::SCIPprintVersion(self.raw, std::ptr::null_mut()) }; - } - - fn write(&self, path: &str, ext: &str) -> Result<(), Retcode> { - let c_path = CString::new(path).unwrap(); - let c_ext = CString::new(ext).unwrap(); - scip_call! { ffi::SCIPwriteOrigProblem( - self.raw, - c_path.as_ptr(), - c_ext.as_ptr(), - true.into(), - ) }; - Ok(()) - } - - fn include_default_plugins(&mut self) -> Result<(), Retcode> { - scip_call!(ffi::SCIPincludeDefaultPlugins(self.raw)); - Ok(()) - } - - fn vars(&self) -> BTreeMap> { - // NOTE: this method should only be called once per SCIP instance - let n_vars = self.n_vars(); - let mut vars = BTreeMap::new(); - let scip_vars = unsafe { ffi::SCIPgetVars(self.raw) }; - for i in 0..n_vars { - let scip_var = unsafe { *scip_vars.add(i) }; - unsafe { - ffi::SCIPcaptureVar(self.raw, scip_var); - } - let var = Rc::new(Variable { raw: scip_var }); - vars.insert(var.index(), var); - } - vars - } - - fn conss(&self) -> Vec> { - // NOTE: this method should only be called once per SCIP instance - let n_conss = self.n_conss(); - let mut conss = Vec::with_capacity(n_conss); - let scip_conss = unsafe { ffi::SCIPgetConss(self.raw) }; - for i in 0..n_conss { - let scip_cons = unsafe { *scip_conss.add(i) }; - unsafe { - ffi::SCIPcaptureCons(self.raw, scip_cons); - } - let cons = Rc::new(Constraint { raw: scip_cons }); - conss.push(cons); - } - conss - } - - fn solve(&mut self) -> Result<(), Retcode> { - scip_call!(ffi::SCIPsolve(self.raw)); - Ok(()) - } - - fn n_sols(&self) -> usize { - unsafe { ffi::SCIPgetNSols(self.raw) as usize } - } - - fn best_sol(&self) -> Solution { - let sol = unsafe { ffi::SCIPgetBestSol(self.raw) }; - - Solution { - scip_ptr: self.raw, - raw: sol, - } - } - - fn obj_val(&self) -> f64 { - unsafe { ffi::SCIPgetPrimalbound(self.raw) } - } - - fn create_var( - &mut self, - lb: f64, - ub: f64, - obj: f64, - name: &str, - var_type: VarType, - ) -> Result { - let name = CString::new(name).unwrap(); - let mut var_ptr = MaybeUninit::uninit(); - scip_call! { ffi::SCIPcreateVarBasic( - self.raw, - var_ptr.as_mut_ptr(), - name.as_ptr(), - lb, - ub, - obj, - var_type.into(), - ) }; - let var_ptr = unsafe { var_ptr.assume_init() }; - scip_call! { ffi::SCIPaddVar(self.raw, var_ptr) }; - Ok(Variable { raw: var_ptr }) - } - - fn create_priced_var( - &mut self, - lb: f64, - ub: f64, - obj: f64, - name: &str, - var_type: VarType, - ) -> Result { - let name = CString::new(name).unwrap(); - let mut var_ptr = MaybeUninit::uninit(); - scip_call! { ffi::SCIPcreateVarBasic( - self.raw, - var_ptr.as_mut_ptr(), - name.as_ptr(), - lb, - ub, - obj, - var_type.into(), - ) }; - let mut var_ptr = unsafe { var_ptr.assume_init() }; - scip_call! { ffi::SCIPaddPricedVar(self.raw, var_ptr, 1.0) }; // 1.0 is used as a default score for now - let mut transformed_var = MaybeUninit::uninit(); - scip_call! { ffi::SCIPgetTransformedVar(self.raw, var_ptr, transformed_var.as_mut_ptr()) }; - let trans_var_ptr = unsafe { transformed_var.assume_init() }; - scip_call! { ffi::SCIPreleaseVar(self.raw, &mut var_ptr) }; - Ok(Variable { raw: trans_var_ptr }) - } - - fn create_cons( - &mut self, - vars: Vec>, - coefs: &[f64], - lhs: 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::SCIPcreateConsBasicLinear( - self.raw, - scip_cons.as_mut_ptr(), - c_name.as_ptr(), - 0, - std::ptr::null_mut(), - std::ptr::null_mut(), - lhs, - rhs, - ) }; - let scip_cons = unsafe { scip_cons.assume_init() }; - for (i, var) in vars.iter().enumerate() { - scip_call! { ffi::SCIPaddCoefLinear(self.raw, scip_cons, var.raw, coefs[i]) }; - } - scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; - Ok(Constraint { raw: scip_cons }) - } - - /// Create set partitioning constraint - fn create_cons_set_part( - &mut self, - vars: Vec>, - name: &str, - ) -> Result { - let c_name = CString::new(name).unwrap(); - let mut scip_cons = MaybeUninit::uninit(); - scip_call! { ffi::SCIPcreateConsBasicSetpart( - self.raw, - scip_cons.as_mut_ptr(), - c_name.as_ptr(), - 0, - std::ptr::null_mut(), - ) }; - let scip_cons = unsafe { scip_cons.assume_init() }; - for var in vars.iter() { - scip_call! { ffi::SCIPaddCoefSetppc(self.raw, scip_cons, var.raw) }; - } - scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; - Ok(Constraint { raw: scip_cons }) - } - - /// Create set cover constraint - fn create_cons_set_cover( - &mut self, - vars: Vec>, - name: &str, - ) -> Result { - let c_name = CString::new(name).unwrap(); - let mut scip_cons = MaybeUninit::uninit(); - scip_call! { ffi::SCIPcreateConsBasicSetcover( - self.raw, - scip_cons.as_mut_ptr(), - c_name.as_ptr(), - 0, - std::ptr::null_mut(), - ) }; - let scip_cons = unsafe { scip_cons.assume_init() }; - for var in vars.iter() { - scip_call! { ffi::SCIPaddCoefSetppc(self.raw, scip_cons, var.raw) }; - } - scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; - Ok(Constraint { raw: scip_cons }) - } - - fn create_cons_quadratic( - &mut self, - lin_vars: Vec>, - lin_coefs: &mut [f64], - quad_vars_1: Vec>, - quad_vars_2: Vec>, - quad_coefs: &mut [f64], - lhs: f64, - rhs: f64, - name: &str, - ) -> Result { - assert_eq!(lin_vars.len(), lin_coefs.len()); - assert!( - lin_vars.len() <= c_int::MAX as usize, - "Number of linear variables exceeds SCIP capabilities" - ); - assert_eq!(quad_vars_1.len(), quad_vars_2.len()); - assert_eq!(quad_vars_1.len(), quad_coefs.len()); - assert!( - quad_vars_1.len() <= c_int::MAX as usize, - "Number of quadratic terms exceeds SCIP capabilities" - ); - - let c_name = CString::new(name).unwrap(); - let mut scip_cons = MaybeUninit::uninit(); - - let get_ptrs = |vars: Vec>| { - vars.into_iter() - .map(|var_rc| var_rc.raw) - .collect::>() - }; - let mut lin_var_ptrs = get_ptrs(lin_vars); - let mut quad_vars_1_ptrs = get_ptrs(quad_vars_1); - let mut quad_vars_2_ptrs = get_ptrs(quad_vars_2); - scip_call! { ffi::SCIPcreateConsBasicQuadraticNonlinear( - self.raw, - scip_cons.as_mut_ptr(), - c_name.as_ptr(), - lin_var_ptrs.len() as c_int, - lin_var_ptrs.as_mut_ptr(), - lin_coefs.as_mut_ptr(), - quad_vars_1_ptrs.len() as c_int, - quad_vars_1_ptrs.as_mut_ptr(), - quad_vars_2_ptrs.as_mut_ptr(), - quad_coefs.as_mut_ptr(), - lhs, - rhs, - ) }; - - let scip_cons = unsafe { scip_cons.assume_init() }; - scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; - Ok(Constraint { raw: scip_cons }) - } - - /// Create set packing constraint - fn create_cons_set_pack( - &mut self, - vars: Vec>, - name: &str, - ) -> Result { - let c_name = CString::new(name).unwrap(); - let mut scip_cons = MaybeUninit::uninit(); - scip_call! { ffi::SCIPcreateConsBasicSetpack( - self.raw, - scip_cons.as_mut_ptr(), - c_name.as_ptr(), - 0, - std::ptr::null_mut(), - ) }; - let scip_cons = unsafe { scip_cons.assume_init() }; - for var in vars.iter() { - scip_call! { ffi::SCIPaddCoefSetppc(self.raw, scip_cons, var.raw) }; - } - scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; - Ok(Constraint { raw: scip_cons }) - } - - /// Create solution - fn create_sol(&self) -> Result { - let mut sol = MaybeUninit::uninit(); - scip_call! { ffi::SCIPcreateSol(self.raw, sol.as_mut_ptr(), std::ptr::null_mut()) } - let sol = unsafe { sol.assume_init() }; - Ok(Solution { - scip_ptr: self.raw, - raw: sol, - }) - } - - /// Add coefficient to set packing/partitioning/covering constraint - fn add_cons_coef_setppc( - &mut self, - cons: Rc, - var: Rc, - ) -> Result<(), Retcode> { - scip_call! { ffi::SCIPaddCoefSetppc(self.raw, cons.raw, var.raw) }; - Ok(()) - } - - fn lp_branching_cands(scip: *mut ffi::SCIP) -> Vec { - let mut lpcands = MaybeUninit::uninit(); - let mut lpcandssol = MaybeUninit::uninit(); - // let mut lpcandsfrac = MaybeUninit::uninit(); - let mut nlpcands = MaybeUninit::uninit(); - // let mut npriolpcands = MaybeUninit::uninit(); - let mut nfracimplvars = MaybeUninit::uninit(); - unsafe { - ffi::SCIPgetLPBranchCands( - scip, - lpcands.as_mut_ptr(), - lpcandssol.as_mut_ptr(), - std::ptr::null_mut(), - nlpcands.as_mut_ptr(), - std::ptr::null_mut(), - nfracimplvars.as_mut_ptr(), - ); - } - let lpcands = unsafe { lpcands.assume_init() }; - let lpcandssol = unsafe { lpcandssol.assume_init() }; - // let lpcandsfrac = unsafe { lpcandsfrac.assume_init() }; - let nlpcands = unsafe { nlpcands.assume_init() }; - // let npriolpcands = unsafe { npriolpcands.assume_init() }; - let mut cands = Vec::with_capacity(nlpcands as usize); - for i in 0..nlpcands { - let var_ptr = unsafe { *lpcands.add(i as usize) }; - let var = Rc::new(Variable { raw: var_ptr }); - let lp_sol_val = unsafe { *lpcandssol.add(i as usize) }; - let frac = lp_sol_val.fract(); - cands.push(BranchingCandidate { - var, - lp_sol_val, - frac, - }); - } - cands - } - - fn branch_var_val( - scip: *mut ffi::SCIP, - var: *mut ffi::SCIP_VAR, - val: f64, - ) -> Result<(), Retcode> { - scip_call! { ffi::SCIPbranchVarVal(scip, var, val, std::ptr::null_mut(), std::ptr::null_mut(),std::ptr::null_mut()) }; - Ok(()) - } - - fn include_eventhdlr( - &self, - name: &str, - desc: &str, - eventhdlr: Box, - ) -> Result<(), Retcode> { - extern "C" fn eventhdlrexec( - _scip: *mut ffi::SCIP, - eventhdlr: *mut ffi::SCIP_EVENTHDLR, - _event: *mut ffi::SCIP_EVENT, - _event_data: *mut ffi::SCIP_EVENTDATA, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) }; - assert!(!data_ptr.is_null()); - let eventhdlr_ptr = data_ptr as *mut Box; - unsafe { (*eventhdlr_ptr).execute() }; - Retcode::Okay.into() - } - - extern "C" fn eventhdlrinit( - scip: *mut ffi::SCIP, - eventhdlr: *mut ffi::SCIP_EVENTHDLR, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) }; - assert!(!data_ptr.is_null()); - let eventhdlr_ptr = data_ptr as *mut Box; - let event_type = unsafe { (*eventhdlr_ptr).var_type() }; - unsafe { - ffi::SCIPcatchEvent( - scip, - event_type.into(), - eventhdlr, - std::ptr::null_mut(), - std::ptr::null_mut(), - ) - } - } - - unsafe extern "C" fn eventhdlrfree( - _scip: *mut ffi::SCIP, - eventhdlr: *mut ffi::SCIP_EVENTHDLR, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) }; - assert!(!data_ptr.is_null()); - let eventhdlr_ptr = data_ptr as *mut Box; - drop(unsafe { Box::from_raw(eventhdlr_ptr) }); - Retcode::Okay.into() - } - - let c_name = CString::new(name).unwrap(); - let c_desc = CString::new(desc).unwrap(); - let eventhdlr_ptr = Box::into_raw(Box::new(eventhdlr)); - - unsafe { - ffi::SCIPincludeEventhdlr( - self.raw, - c_name.as_ptr(), - c_desc.as_ptr(), - None, - Some(eventhdlrfree), - Some(eventhdlrinit), - None, - None, - None, - None, - Some(eventhdlrexec), - eventhdlr_ptr as *mut ffi::SCIP_EVENTHDLRDATA, - ); - } - - Ok(()) - } - - fn include_branch_rule( - &self, - name: &str, - desc: &str, - priority: i32, - maxdepth: i32, - maxbounddist: f64, - rule: Box, - ) -> Result<(), Retcode> { - let c_name = CString::new(name).unwrap(); - let c_desc = CString::new(desc).unwrap(); - - // TODO: Add rest of branching rule plugin callbacks - - extern "C" fn branchexeclp( - scip: *mut ffi::SCIP, - branchrule: *mut ffi::SCIP_BRANCHRULE, - _: u32, - res: *mut ffi::SCIP_RESULT, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPbranchruleGetData(branchrule) }; - assert!(!data_ptr.is_null()); - let rule_ptr = data_ptr as *mut Box; - let cands = ScipPtr::lp_branching_cands(scip); - let branching_res = unsafe { (*rule_ptr).execute(cands) }; - - if let BranchingResult::BranchOn(cand) = branching_res.clone() { - ScipPtr::branch_var_val(scip, cand.var.raw, cand.lp_sol_val).unwrap(); - }; - - if branching_res == BranchingResult::CustomBranching { - assert!( - unsafe { ffi::SCIPgetNChildren(scip) > 0 }, - "Custom branching rule must create at least one child node" - ) - } - - unsafe { *res = branching_res.into() }; - Retcode::Okay.into() - } - - extern "C" fn branchfree( - _scip: *mut ffi::SCIP, - branchrule: *mut ffi::SCIP_BRANCHRULE, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPbranchruleGetData(branchrule) }; - assert!(!data_ptr.is_null()); - drop(unsafe { Box::from_raw(data_ptr as *mut Box) }); - Retcode::Okay.into() - } - - let rule_ptr = Box::into_raw(Box::new(rule)); - let branchrule_faker = rule_ptr as *mut ffi::SCIP_BranchruleData; - - scip_call!(ffi::SCIPincludeBranchrule( - self.raw, - c_name.as_ptr(), - c_desc.as_ptr(), - priority, - maxdepth, - maxbounddist, - None, - Some(branchfree), - None, - None, - None, - None, - Some(branchexeclp), - None, - None, - branchrule_faker, - )); - - Ok(()) - } - - fn include_pricer( - &self, - name: &str, - desc: &str, - priority: i32, - delay: bool, - pricer: Box, - ) -> Result<(), Retcode> { - let c_name = CString::new(name).unwrap(); - let c_desc = CString::new(desc).unwrap(); - - fn call_pricer( - scip: *mut ffi::SCIP, - pricer: *mut ffi::SCIP_PRICER, - lowerbound: *mut f64, - stopearly: *mut ::std::os::raw::c_uint, - result: *mut ffi::SCIP_RESULT, - farkas: bool, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPpricerGetData(pricer) }; - assert!(!data_ptr.is_null()); - let pricer_ptr = data_ptr as *mut Box; - - let n_vars_before = unsafe { ffi::SCIPgetNVars(scip) }; - let pricing_res = unsafe { (*pricer_ptr).generate_columns(farkas) }; - - if !farkas { - if let Some(lb) = pricing_res.lower_bound { - unsafe { *lowerbound = lb }; - } - if pricing_res.state == PricerResultState::StopEarly { - unsafe { *stopearly = 1 }; - } - } - - if farkas && pricing_res.state == PricerResultState::StopEarly { - panic!("Farkas pricing should never stop early as LP would remain infeasible"); - } - - if pricing_res.state == PricerResultState::FoundColumns { - let n_vars_after = unsafe { ffi::SCIPgetNVars(scip) }; - assert!(n_vars_before < n_vars_after); - } - - unsafe { *result = pricing_res.state.into() }; - Retcode::Okay.into() - } - - unsafe extern "C" fn pricerredcost( - scip: *mut ffi::SCIP, - pricer: *mut ffi::SCIP_PRICER, - lowerbound: *mut f64, - stopearly: *mut ::std::os::raw::c_uint, - result: *mut ffi::SCIP_RESULT, - ) -> ffi::SCIP_Retcode { - call_pricer(scip, pricer, lowerbound, stopearly, result, false) - } - - unsafe extern "C" fn pricerfakas( - scip: *mut ffi::SCIP, - pricer: *mut ffi::SCIP_PRICER, - result: *mut ffi::SCIP_RESULT, - ) -> ffi::SCIP_Retcode { - call_pricer( - scip, - pricer, - std::ptr::null_mut(), - std::ptr::null_mut(), - result, - true, - ) - } - - unsafe extern "C" fn pricerfree( - _scip: *mut ffi::SCIP, - pricer: *mut ffi::SCIP_PRICER, - ) -> ffi::SCIP_Retcode { - let data_ptr = unsafe { ffi::SCIPpricerGetData(pricer) }; - assert!(!data_ptr.is_null()); - drop(unsafe { Box::from_raw(data_ptr as *mut Box) }); - Retcode::Okay.into() - } - - let pricer_ptr = Box::into_raw(Box::new(pricer)); - let pricer_faker = pricer_ptr as *mut ffi::SCIP_PricerData; - - scip_call!(ffi::SCIPincludePricer( - self.raw, - c_name.as_ptr(), - c_desc.as_ptr(), - priority, - delay.into(), - None, - Some(pricerfree), - None, - None, - None, - None, - Some(pricerredcost), - Some(pricerfakas), - pricer_faker, - )); - - unsafe { - ffi::SCIPactivatePricer(self.raw, ffi::SCIPfindPricer(self.raw, c_name.as_ptr())); - } - - Ok(()) - } - - fn add_cons_coef( - &mut self, - cons: Rc, - var: Rc, - coef: f64, - ) -> Result<(), Retcode> { - let cons_is_transformed = unsafe { ffi::SCIPconsIsTransformed(cons.raw) } == 1; - let var_is_transformed = unsafe { ffi::SCIPvarIsTransformed(var.raw) } == 1; - let cons_ptr = if !cons_is_transformed && var_is_transformed { - let mut transformed_cons = MaybeUninit::<*mut ffi::SCIP_Cons>::uninit(); - scip_call!(ffi::SCIPgetTransformedCons( - self.raw, - cons.raw, - transformed_cons.as_mut_ptr() - )); - unsafe { transformed_cons.assume_init() } - } else { - cons.raw - }; - - let var_ptr = if cons_is_transformed && !var_is_transformed { - let mut transformed_var = MaybeUninit::<*mut ffi::SCIP_Var>::uninit(); - scip_call!(ffi::SCIPgetTransformedVar( - self.raw, - var.raw, - transformed_var.as_mut_ptr() - )); - unsafe { transformed_var.assume_init() } - } else { - var.raw - }; - - scip_call! { ffi::SCIPaddCoefLinear(self.raw, cons_ptr, var_ptr, coef) }; - Ok(()) - } - - fn set_cons_modifiable( - &mut self, - cons: Rc, - modifiable: bool, - ) -> Result<(), Retcode> { - scip_call!(ffi::SCIPsetConsModifiable( - self.raw, - cons.raw, - modifiable.into() - )); - Ok(()) - } - - fn n_nodes(&self) -> usize { - unsafe { ffi::SCIPgetNNodes(self.raw) as usize } - } - - fn solving_time(&self) -> f64 { - unsafe { ffi::SCIPgetSolvingTime(self.raw) } - } - - fn n_lp_iterations(&self) -> usize { - unsafe { ffi::SCIPgetNLPIterations(self.raw) as usize } - } - - fn focus_node(&self) -> Node { - Node { - raw: unsafe { ffi::SCIPgetFocusNode(self.raw) }, - } - } - - fn create_child(&mut self) -> Result { - let mut node_ptr = MaybeUninit::uninit(); - scip_call!(ffi::SCIPcreateChild( - self.raw, - node_ptr.as_mut_ptr(), - 0., - ffi::SCIPgetLocalTransEstimate(self.raw), // TODO: pass that as an argument - )); - - let node_ptr = unsafe { node_ptr.assume_init() }; - Ok(Node { raw: node_ptr }) - } - - fn add_sol(&self, sol: Solution) -> Result { - let mut stored = MaybeUninit::uninit(); - scip_call!(ffi::SCIPaddSol(self.raw, sol.raw, stored.as_mut_ptr())); - let stored = unsafe { stored.assume_init() }; - Ok(stored == 1) - } -} - -impl Drop for ScipPtr { - fn drop(&mut self) { - if self.consumed { - return; - } - // Rust Model struct keeps at most one copy of each variable and constraint pointers - // so we need to release them before freeing the SCIP instance - - // first check if we are in a stage where we have variables and constraints - let scip_stage = unsafe { ffi::SCIPgetStage(self.raw) }; - if scip_stage == ffi::SCIP_Stage_SCIP_STAGE_PROBLEM - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_TRANSFORMED - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_INITPRESOLVE - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_PRESOLVING - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_EXITPRESOLVE - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_PRESOLVED - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_INITSOLVE - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_SOLVING - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_SOLVED - || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_EXITSOLVE - { - // release original variables - let n_vars = unsafe { ffi::SCIPgetNOrigVars(self.raw) }; - let vars = unsafe { ffi::SCIPgetOrigVars(self.raw) }; - for i in 0..n_vars { - let mut var = unsafe { *vars.add(i as usize) }; - scip_call_panic!(ffi::SCIPreleaseVar(self.raw, &mut var)); - } - - // release constraints - let n_conss = unsafe { ffi::SCIPgetNOrigConss(self.raw) }; - let conss = unsafe { ffi::SCIPgetOrigConss(self.raw) }; - for i in 0..n_conss { - let mut cons = unsafe { *conss.add(i as usize) }; - scip_call_panic!(ffi::SCIPreleaseCons(self.raw, &mut cons)); - } - } - - // free SCIP instance - unsafe { ffi::SCIPfree(&mut self.raw) }; - } -} +use crate::{BranchRule, HeurTiming, Heuristic, Pricer}; /// Represents an optimization model. #[non_exhaustive] @@ -1353,6 +514,54 @@ impl Model { self } + /// Include a new primal heuristic in the model. + /// + /// # Arguments + /// + /// * `name` - The name of the heuristic. This should be a unique identifier. + /// * `desc` - A brief description of the heuristic. This is used for informational purposes. + /// * `priority` - The priority of the heuristic. When SCIP decides which heuristic to call, it considers their priorities. A higher value indicates a higher priority. + /// * `dispchar` - The display character of the heuristic (used in logs). + /// * `freq` - The frequency for calling the heuristic in the tree; 1 means at every node, 2 means at every other node and so on, -1 turns off the heuristic. + /// * `freqofs` - The frequency offset for calling the heuristic in the tree; it defines the depth of the branching tree at which the primal heuristic is executed for the first time. + /// * `maxdepth` - The maximum depth level up to which this heuristic should be used. If this is -1, the heuristic can be used at any depth. + /// * `timing` - The timing mask of the heuristic. + /// * `usessubscip` - Should the heuristic use a secondary SCIP instance? + /// * `heur` - The heuristic to be included. This should be a Box of an object that implements the `Heur` trait, and represents the heuristic data. + /// + /// # Returns + /// + /// This function returns the `Model` instance for which the heuristic was included. This allows for method chaining. + pub fn include_heur( + self, + name: &str, + desc: &str, + priority: i32, + dispchar: char, + freq: i32, + freqofs: i32, + maxdepth: i32, + timing: HeurTiming, + usessubscip: bool, + heur: Box, + ) -> Self { + self.scip + .include_heur( + name, + desc, + priority, + dispchar, + freq, + freqofs, + maxdepth, + timing, + usessubscip, + heur, + ) + .expect("Failed to include heuristic at state ProblemCreated"); + self + } + /// Includes a new event handler in the model. /// /// # Arguments @@ -1987,7 +1196,9 @@ mod tests { model.write("test.lp", "lp").unwrap(); let read_model = Model::new() - .include_default_plugins().read_prob("test.lp").unwrap(); + .include_default_plugins() + .read_prob("test.lp") + .unwrap(); let solved = model.solve(); let read_solved = read_model.solve(); @@ -1998,28 +1209,27 @@ mod tests { fs::remove_file("test.lp").unwrap(); } - #[test] fn print_version() { Model::new().print_version(); } - #[test] fn set_int_param() { let res = Model::new() .hide_output() - .set_int_param("display/verblevel", -1).unwrap_err(); + .set_int_param("display/verblevel", -1) + .unwrap_err(); assert_eq!(res, Retcode::ParameterWrongVal); } - #[test] fn set_real_param() { let model = Model::new() .hide_output() - .set_real_param("limits/time", 0.).unwrap() + .set_real_param("limits/time", 0.) + .unwrap() .include_default_plugins() .read_prob("data/test/simple.lp") .unwrap() diff --git a/src/scip.rs b/src/scip.rs new file mode 100644 index 0000000..35ab9cb --- /dev/null +++ b/src/scip.rs @@ -0,0 +1,931 @@ +use crate::branchrule::{BranchRule, BranchingCandidate}; +use crate::pricer::{Pricer, PricerResultState}; +use crate::{ + ffi, scip_call_panic, BranchingResult, Constraint, Eventhdlr, HeurResult, Node, ObjSense, + ParamSetting, Retcode, Solution, Status, VarType, Variable, +}; +use crate::{scip_call, HeurTiming, Heuristic}; +use core::panic; +use std::collections::BTreeMap; +use std::ffi::{c_int, CStr, CString}; +use std::mem::MaybeUninit; +use std::rc::Rc; + +#[non_exhaustive] +#[derive(Debug)] +pub(crate) struct ScipPtr { + pub(crate) raw: *mut ffi::SCIP, + consumed: bool, +} + +impl ScipPtr { + pub(crate) fn new() -> Self { + let mut scip_ptr = MaybeUninit::uninit(); + scip_call_panic!(ffi::SCIPcreate(scip_ptr.as_mut_ptr())); + let scip_ptr = unsafe { scip_ptr.assume_init() }; + ScipPtr { + raw: scip_ptr, + consumed: false, + } + } + + pub(crate) fn clone(&self) -> Self { + ScipPtr { + raw: self.raw, + consumed: true, + } + } + + pub(crate) fn set_str_param(&mut self, param: &str, value: &str) -> Result<(), Retcode> { + let param = CString::new(param).unwrap(); + let value = CString::new(value).unwrap(); + scip_call! { ffi::SCIPsetStringParam(self.raw, param.as_ptr(), value.as_ptr()) }; + Ok(()) + } + + pub(crate) fn set_int_param(&mut self, param: &str, value: i32) -> Result<(), Retcode> { + let param = CString::new(param).unwrap(); + scip_call! { ffi::SCIPsetIntParam(self.raw, param.as_ptr(), value) }; + Ok(()) + } + + pub(crate) fn set_longint_param(&mut self, param: &str, value: i64) -> Result<(), Retcode> { + let param = CString::new(param).unwrap(); + scip_call! { ffi::SCIPsetLongintParam(self.raw, param.as_ptr(), value) }; + Ok(()) + } + + pub(crate) fn set_real_param(&mut self, param: &str, value: f64) -> Result<(), Retcode> { + let param = CString::new(param).unwrap(); + scip_call! { ffi::SCIPsetRealParam(self.raw, param.as_ptr(), value) }; + Ok(()) + } + + pub(crate) fn set_presolving(&mut self, presolving: ParamSetting) -> Result<(), Retcode> { + scip_call! { ffi::SCIPsetPresolving(self.raw, presolving.into(), true.into()) }; + Ok(()) + } + + pub(crate) fn set_separating(&mut self, separating: ParamSetting) -> Result<(), Retcode> { + scip_call! { ffi::SCIPsetSeparating(self.raw, separating.into(), true.into()) }; + Ok(()) + } + + pub(crate) fn set_heuristics(&mut self, heuristics: ParamSetting) -> Result<(), Retcode> { + scip_call! { ffi::SCIPsetHeuristics(self.raw, heuristics.into(), true.into()) }; + Ok(()) + } + + pub(crate) fn create_prob(&mut self, name: &str) -> Result<(), Retcode> { + let name = CString::new(name).unwrap(); + scip_call!(ffi::SCIPcreateProbBasic(self.raw, name.as_ptr())); + Ok(()) + } + + pub(crate) fn read_prob(&mut self, filename: &str) -> Result<(), Retcode> { + let filename = CString::new(filename).unwrap(); + scip_call!(ffi::SCIPreadProb( + self.raw, + filename.as_ptr(), + std::ptr::null_mut() + )); + Ok(()) + } + + pub(crate) fn set_obj_sense(&mut self, sense: ObjSense) -> Result<(), Retcode> { + scip_call!(ffi::SCIPsetObjsense(self.raw, sense.into())); + Ok(()) + } + + pub(crate) fn n_vars(&self) -> usize { + unsafe { ffi::SCIPgetNVars(self.raw) as usize } + } + + pub(crate) fn n_conss(&self) -> usize { + unsafe { ffi::SCIPgetNConss(self.raw) as usize } + } + + pub(crate) fn status(&self) -> Status { + let status = unsafe { ffi::SCIPgetStatus(self.raw) }; + status.try_into().expect("Unknown SCIP status") + } + + pub(crate) fn print_version(&self) { + unsafe { ffi::SCIPprintVersion(self.raw, std::ptr::null_mut()) }; + } + + pub(crate) fn write(&self, path: &str, ext: &str) -> Result<(), Retcode> { + let c_path = CString::new(path).unwrap(); + let c_ext = CString::new(ext).unwrap(); + scip_call! { ffi::SCIPwriteOrigProblem( + self.raw, + c_path.as_ptr(), + c_ext.as_ptr(), + true.into(), + ) }; + Ok(()) + } + + pub(crate) fn include_default_plugins(&mut self) -> Result<(), Retcode> { + scip_call!(ffi::SCIPincludeDefaultPlugins(self.raw)); + Ok(()) + } + + pub(crate) fn vars(&self) -> BTreeMap> { + // NOTE: this method should only be called once per SCIP instance + let n_vars = self.n_vars(); + let mut vars = BTreeMap::new(); + let scip_vars = unsafe { ffi::SCIPgetVars(self.raw) }; + for i in 0..n_vars { + let scip_var = unsafe { *scip_vars.add(i) }; + unsafe { + ffi::SCIPcaptureVar(self.raw, scip_var); + } + let var = Rc::new(Variable { raw: scip_var }); + vars.insert(var.index(), var); + } + vars + } + + pub(crate) fn conss(&self) -> Vec> { + // NOTE: this method should only be called once per SCIP instance + let n_conss = self.n_conss(); + let mut conss = Vec::with_capacity(n_conss); + let scip_conss = unsafe { ffi::SCIPgetConss(self.raw) }; + for i in 0..n_conss { + let scip_cons = unsafe { *scip_conss.add(i) }; + unsafe { + ffi::SCIPcaptureCons(self.raw, scip_cons); + } + let cons = Rc::new(Constraint { raw: scip_cons }); + conss.push(cons); + } + conss + } + + pub(crate) fn solve(&mut self) -> Result<(), Retcode> { + scip_call!(ffi::SCIPsolve(self.raw)); + Ok(()) + } + + pub(crate) fn n_sols(&self) -> usize { + unsafe { ffi::SCIPgetNSols(self.raw) as usize } + } + + pub(crate) fn best_sol(&self) -> Solution { + let sol = unsafe { ffi::SCIPgetBestSol(self.raw) }; + + Solution { + scip_ptr: self.raw, + raw: sol, + } + } + + pub(crate) fn obj_val(&self) -> f64 { + unsafe { ffi::SCIPgetPrimalbound(self.raw) } + } + + pub(crate) fn create_var( + &mut self, + lb: f64, + ub: f64, + obj: f64, + name: &str, + var_type: VarType, + ) -> Result { + let name = CString::new(name).unwrap(); + let mut var_ptr = MaybeUninit::uninit(); + scip_call! { ffi::SCIPcreateVarBasic( + self.raw, + var_ptr.as_mut_ptr(), + name.as_ptr(), + lb, + ub, + obj, + var_type.into(), + ) }; + let var_ptr = unsafe { var_ptr.assume_init() }; + scip_call! { ffi::SCIPaddVar(self.raw, var_ptr) }; + Ok(Variable { raw: var_ptr }) + } + + pub(crate) fn create_priced_var( + &mut self, + lb: f64, + ub: f64, + obj: f64, + name: &str, + var_type: VarType, + ) -> Result { + let name = CString::new(name).unwrap(); + let mut var_ptr = MaybeUninit::uninit(); + scip_call! { ffi::SCIPcreateVarBasic( + self.raw, + var_ptr.as_mut_ptr(), + name.as_ptr(), + lb, + ub, + obj, + var_type.into(), + ) }; + let mut var_ptr = unsafe { var_ptr.assume_init() }; + scip_call! { ffi::SCIPaddPricedVar(self.raw, var_ptr, 1.0) }; // 1.0 is used as a default score for now + let mut transformed_var = MaybeUninit::uninit(); + scip_call! { ffi::SCIPgetTransformedVar(self.raw, var_ptr, transformed_var.as_mut_ptr()) }; + let trans_var_ptr = unsafe { transformed_var.assume_init() }; + scip_call! { ffi::SCIPreleaseVar(self.raw, &mut var_ptr) }; + Ok(Variable { raw: trans_var_ptr }) + } + + pub(crate) fn create_cons( + &mut self, + vars: Vec>, + coefs: &[f64], + lhs: 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::SCIPcreateConsBasicLinear( + self.raw, + scip_cons.as_mut_ptr(), + c_name.as_ptr(), + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + lhs, + rhs, + ) }; + let scip_cons = unsafe { scip_cons.assume_init() }; + for (i, var) in vars.iter().enumerate() { + scip_call! { ffi::SCIPaddCoefLinear(self.raw, scip_cons, var.raw, coefs[i]) }; + } + scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; + Ok(Constraint { raw: scip_cons }) + } + + /// Create set partitioning constraint + pub(crate) fn create_cons_set_part( + &mut self, + vars: Vec>, + name: &str, + ) -> Result { + let c_name = CString::new(name).unwrap(); + let mut scip_cons = MaybeUninit::uninit(); + scip_call! { ffi::SCIPcreateConsBasicSetpart( + self.raw, + scip_cons.as_mut_ptr(), + c_name.as_ptr(), + 0, + std::ptr::null_mut(), + ) }; + let scip_cons = unsafe { scip_cons.assume_init() }; + for var in vars.iter() { + scip_call! { ffi::SCIPaddCoefSetppc(self.raw, scip_cons, var.raw) }; + } + scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; + Ok(Constraint { raw: scip_cons }) + } + + /// Create set cover constraint + pub(crate) fn create_cons_set_cover( + &mut self, + vars: Vec>, + name: &str, + ) -> Result { + let c_name = CString::new(name).unwrap(); + let mut scip_cons = MaybeUninit::uninit(); + scip_call! { ffi::SCIPcreateConsBasicSetcover( + self.raw, + scip_cons.as_mut_ptr(), + c_name.as_ptr(), + 0, + std::ptr::null_mut(), + ) }; + let scip_cons = unsafe { scip_cons.assume_init() }; + for var in vars.iter() { + scip_call! { ffi::SCIPaddCoefSetppc(self.raw, scip_cons, var.raw) }; + } + scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; + Ok(Constraint { raw: scip_cons }) + } + + pub(crate) fn create_cons_quadratic( + &mut self, + lin_vars: Vec>, + lin_coefs: &mut [f64], + quad_vars_1: Vec>, + quad_vars_2: Vec>, + quad_coefs: &mut [f64], + lhs: f64, + rhs: f64, + name: &str, + ) -> Result { + assert_eq!(lin_vars.len(), lin_coefs.len()); + assert!( + lin_vars.len() <= c_int::MAX as usize, + "Number of linear variables exceeds SCIP capabilities" + ); + assert_eq!(quad_vars_1.len(), quad_vars_2.len()); + assert_eq!(quad_vars_1.len(), quad_coefs.len()); + assert!( + quad_vars_1.len() <= c_int::MAX as usize, + "Number of quadratic terms exceeds SCIP capabilities" + ); + + let c_name = CString::new(name).unwrap(); + let mut scip_cons = MaybeUninit::uninit(); + + let get_ptrs = |vars: Vec>| { + vars.into_iter() + .map(|var_rc| var_rc.raw) + .collect::>() + }; + let mut lin_var_ptrs = get_ptrs(lin_vars); + let mut quad_vars_1_ptrs = get_ptrs(quad_vars_1); + let mut quad_vars_2_ptrs = get_ptrs(quad_vars_2); + scip_call! { ffi::SCIPcreateConsBasicQuadraticNonlinear( + self.raw, + scip_cons.as_mut_ptr(), + c_name.as_ptr(), + lin_var_ptrs.len() as c_int, + lin_var_ptrs.as_mut_ptr(), + lin_coefs.as_mut_ptr(), + quad_vars_1_ptrs.len() as c_int, + quad_vars_1_ptrs.as_mut_ptr(), + quad_vars_2_ptrs.as_mut_ptr(), + quad_coefs.as_mut_ptr(), + lhs, + rhs, + ) }; + + let scip_cons = unsafe { scip_cons.assume_init() }; + scip_call! { ffi::SCIPaddCons(self.raw, scip_cons) }; + Ok(Constraint { raw: scip_cons }) + } + + /// Create set packing constraint + pub(crate) fn create_cons_set_pack( + &mut self, + vars: Vec>, + name: &str, + ) -> Result { + let c_name = CString::new(name).unwrap(); + let mut scip_cons = MaybeUninit::uninit(); + scip_call! { ffi::SCIPcreateConsBasicSetpack( + self.raw, + scip_cons.as_mut_ptr(), + c_name.as_ptr(), + 0, + std::ptr::null_mut(), + ) }; + let scip_cons = unsafe { scip_cons.assume_init() }; + for var in vars.iter() { + scip_call! { ffi::SCIPaddCoefSetppc(self.raw, scip_cons, var.raw) }; + } + 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(); + scip_call! { ffi::SCIPcreateSol(self.raw, sol.as_mut_ptr(), std::ptr::null_mut()) } + let sol = unsafe { sol.assume_init() }; + Ok(Solution { + scip_ptr: self.raw, + raw: sol, + }) + } + + /// Add coefficient to set packing/partitioning/covering constraint + pub(crate) fn add_cons_coef_setppc( + &mut self, + cons: Rc, + var: Rc, + ) -> Result<(), Retcode> { + scip_call! { ffi::SCIPaddCoefSetppc(self.raw, cons.raw, var.raw) }; + Ok(()) + } + + pub(crate) fn lp_branching_cands(scip: *mut ffi::SCIP) -> Vec { + let mut lpcands = MaybeUninit::uninit(); + let mut lpcandssol = MaybeUninit::uninit(); + // let mut lpcandsfrac = MaybeUninit::uninit(); + let mut nlpcands = MaybeUninit::uninit(); + // let mut npriolpcands = MaybeUninit::uninit(); + let mut nfracimplvars = MaybeUninit::uninit(); + unsafe { + ffi::SCIPgetLPBranchCands( + scip, + lpcands.as_mut_ptr(), + lpcandssol.as_mut_ptr(), + std::ptr::null_mut(), + nlpcands.as_mut_ptr(), + std::ptr::null_mut(), + nfracimplvars.as_mut_ptr(), + ); + } + let lpcands = unsafe { lpcands.assume_init() }; + let lpcandssol = unsafe { lpcandssol.assume_init() }; + // let lpcandsfrac = unsafe { lpcandsfrac.assume_init() }; + let nlpcands = unsafe { nlpcands.assume_init() }; + // let npriolpcands = unsafe { npriolpcands.assume_init() }; + let mut cands = Vec::with_capacity(nlpcands as usize); + for i in 0..nlpcands { + let var_ptr = unsafe { *lpcands.add(i as usize) }; + let var = Rc::new(Variable { raw: var_ptr }); + let lp_sol_val = unsafe { *lpcandssol.add(i as usize) }; + let frac = lp_sol_val.fract(); + cands.push(BranchingCandidate { + var, + lp_sol_val, + frac, + }); + } + cands + } + + pub(crate) fn branch_var_val( + scip: *mut ffi::SCIP, + var: *mut ffi::SCIP_VAR, + val: f64, + ) -> Result<(), Retcode> { + scip_call! { ffi::SCIPbranchVarVal(scip, var, val, std::ptr::null_mut(), std::ptr::null_mut(),std::ptr::null_mut()) }; + Ok(()) + } + + pub(crate) fn include_eventhdlr( + &self, + name: &str, + desc: &str, + eventhdlr: Box, + ) -> Result<(), Retcode> { + extern "C" fn eventhdlrexec( + _scip: *mut ffi::SCIP, + eventhdlr: *mut ffi::SCIP_EVENTHDLR, + _event: *mut ffi::SCIP_EVENT, + _event_data: *mut ffi::SCIP_EVENTDATA, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) }; + assert!(!data_ptr.is_null()); + let eventhdlr_ptr = data_ptr as *mut Box; + unsafe { (*eventhdlr_ptr).execute() }; + Retcode::Okay.into() + } + + extern "C" fn eventhdlrinit( + scip: *mut ffi::SCIP, + eventhdlr: *mut ffi::SCIP_EVENTHDLR, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) }; + assert!(!data_ptr.is_null()); + let eventhdlr_ptr = data_ptr as *mut Box; + let event_type = unsafe { (*eventhdlr_ptr).get_type() }; + unsafe { + ffi::SCIPcatchEvent( + scip, + event_type.into(), + eventhdlr, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) + } + } + + unsafe extern "C" fn eventhdlrfree( + _scip: *mut ffi::SCIP, + eventhdlr: *mut ffi::SCIP_EVENTHDLR, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPeventhdlrGetData(eventhdlr) }; + assert!(!data_ptr.is_null()); + let eventhdlr_ptr = data_ptr as *mut Box; + drop(unsafe { Box::from_raw(eventhdlr_ptr) }); + Retcode::Okay.into() + } + + let c_name = CString::new(name).unwrap(); + let c_desc = CString::new(desc).unwrap(); + let eventhdlr_ptr = Box::into_raw(Box::new(eventhdlr)); + + unsafe { + ffi::SCIPincludeEventhdlr( + self.raw, + c_name.as_ptr(), + c_desc.as_ptr(), + None, + Some(eventhdlrfree), + Some(eventhdlrinit), + None, + None, + None, + None, + Some(eventhdlrexec), + eventhdlr_ptr as *mut ffi::SCIP_EVENTHDLRDATA, + ); + } + + Ok(()) + } + + pub(crate) fn include_branch_rule( + &self, + name: &str, + desc: &str, + priority: i32, + maxdepth: i32, + maxbounddist: f64, + rule: Box, + ) -> Result<(), Retcode> { + let c_name = CString::new(name).unwrap(); + let c_desc = CString::new(desc).unwrap(); + + // TODO: Add rest of branching rule plugin callbacks + + extern "C" fn branchexeclp( + scip: *mut ffi::SCIP, + branchrule: *mut ffi::SCIP_BRANCHRULE, + _: u32, + res: *mut ffi::SCIP_RESULT, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPbranchruleGetData(branchrule) }; + assert!(!data_ptr.is_null()); + let rule_ptr = data_ptr as *mut Box; + let cands = ScipPtr::lp_branching_cands(scip); + let branching_res = unsafe { (*rule_ptr).execute(cands) }; + + if let BranchingResult::BranchOn(cand) = branching_res.clone() { + ScipPtr::branch_var_val(scip, cand.var.raw, cand.lp_sol_val).unwrap(); + }; + + if branching_res == BranchingResult::CustomBranching { + assert!( + unsafe { ffi::SCIPgetNChildren(scip) > 0 }, + "Custom branching rule must create at least one child node" + ) + } + + unsafe { *res = branching_res.into() }; + Retcode::Okay.into() + } + + extern "C" fn branchfree( + _scip: *mut ffi::SCIP, + branchrule: *mut ffi::SCIP_BRANCHRULE, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPbranchruleGetData(branchrule) }; + assert!(!data_ptr.is_null()); + drop(unsafe { Box::from_raw(data_ptr as *mut Box) }); + Retcode::Okay.into() + } + + let rule_ptr = Box::into_raw(Box::new(rule)); + let branchrule_faker = rule_ptr as *mut ffi::SCIP_BranchruleData; + + scip_call!(ffi::SCIPincludeBranchrule( + self.raw, + c_name.as_ptr(), + c_desc.as_ptr(), + priority, + maxdepth, + maxbounddist, + None, + Some(branchfree), + None, + None, + None, + None, + Some(branchexeclp), + None, + None, + branchrule_faker, + )); + + Ok(()) + } + + pub(crate) fn include_pricer( + &self, + name: &str, + desc: &str, + priority: i32, + delay: bool, + pricer: Box, + ) -> Result<(), Retcode> { + let c_name = CString::new(name).unwrap(); + let c_desc = CString::new(desc).unwrap(); + + pub(crate) fn call_pricer( + scip: *mut ffi::SCIP, + pricer: *mut ffi::SCIP_PRICER, + lowerbound: *mut f64, + stopearly: *mut ::std::os::raw::c_uint, + result: *mut ffi::SCIP_RESULT, + farkas: bool, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPpricerGetData(pricer) }; + assert!(!data_ptr.is_null()); + let pricer_ptr = data_ptr as *mut Box; + + let n_vars_before = unsafe { ffi::SCIPgetNVars(scip) }; + let pricing_res = unsafe { (*pricer_ptr).generate_columns(farkas) }; + + if !farkas { + if let Some(lb) = pricing_res.lower_bound { + unsafe { *lowerbound = lb }; + } + if pricing_res.state == PricerResultState::StopEarly { + unsafe { *stopearly = 1 }; + } + } + + if farkas && pricing_res.state == PricerResultState::StopEarly { + panic!("Farkas pricing should never stop early as LP would remain infeasible"); + } + + if pricing_res.state == PricerResultState::FoundColumns { + let n_vars_after = unsafe { ffi::SCIPgetNVars(scip) }; + assert!(n_vars_before < n_vars_after); + } + + unsafe { *result = pricing_res.state.into() }; + Retcode::Okay.into() + } + + unsafe extern "C" fn pricerredcost( + scip: *mut ffi::SCIP, + pricer: *mut ffi::SCIP_PRICER, + lowerbound: *mut f64, + stopearly: *mut ::std::os::raw::c_uint, + result: *mut ffi::SCIP_RESULT, + ) -> ffi::SCIP_Retcode { + call_pricer(scip, pricer, lowerbound, stopearly, result, false) + } + + unsafe extern "C" fn pricerfakas( + scip: *mut ffi::SCIP, + pricer: *mut ffi::SCIP_PRICER, + result: *mut ffi::SCIP_RESULT, + ) -> ffi::SCIP_Retcode { + call_pricer( + scip, + pricer, + std::ptr::null_mut(), + std::ptr::null_mut(), + result, + true, + ) + } + + unsafe extern "C" fn pricerfree( + _scip: *mut ffi::SCIP, + pricer: *mut ffi::SCIP_PRICER, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPpricerGetData(pricer) }; + assert!(!data_ptr.is_null()); + drop(unsafe { Box::from_raw(data_ptr as *mut Box) }); + Retcode::Okay.into() + } + + let pricer_ptr = Box::into_raw(Box::new(pricer)); + let pricer_faker = pricer_ptr as *mut ffi::SCIP_PricerData; + + scip_call!(ffi::SCIPincludePricer( + self.raw, + c_name.as_ptr(), + c_desc.as_ptr(), + priority, + delay.into(), + None, + Some(pricerfree), + None, + None, + None, + None, + Some(pricerredcost), + Some(pricerfakas), + pricer_faker, + )); + + unsafe { + ffi::SCIPactivatePricer(self.raw, ffi::SCIPfindPricer(self.raw, c_name.as_ptr())); + } + + Ok(()) + } + + pub(crate) fn include_heur( + &self, + name: &str, + desc: &str, + priority: i32, + dispchar: char, + freq: i32, + freqofs: i32, + maxdepth: i32, + timing: HeurTiming, + usessubscip: bool, + heur: Box, + ) -> Result<(), Retcode> { + let c_name = CString::new(name).unwrap(); + let c_desc = CString::new(desc).unwrap(); + + extern "C" fn heurexec( + scip: *mut ffi::SCIP, + heur: *mut ffi::SCIP_HEUR, + heurtiming: ffi::SCIP_HEURTIMING, + nodeinfeasible: ::std::os::raw::c_uint, + result: *mut ffi::SCIP_RESULT, + ) -> ffi::SCIP_RETCODE { + let data_ptr = unsafe { ffi::SCIPheurGetData(heur) }; + assert!(!data_ptr.is_null()); + let rule_ptr = data_ptr as *mut Box; + + let current_n_sols = unsafe { ffi::SCIPgetNSols(scip) }; + let heur_res = unsafe { (*rule_ptr).execute(heurtiming.into(), nodeinfeasible != 0) }; + if heur_res == HeurResult::FoundSol { + let new_n_sols = unsafe { ffi::SCIPgetNSols(scip) }; + + if new_n_sols <= current_n_sols { + let heur_name = + unsafe { CStr::from_ptr(ffi::SCIPheurGetName(heur)).to_str().unwrap() }; + panic!( + "Heuristic {} returned result {:?}, but no solutions were added", + heur_name, heur_res + ); + } + } + + unsafe { *result = heur_res.into() }; + Retcode::Okay.into() + } + + extern "C" fn heurfree( + _scip: *mut ffi::SCIP, + heur: *mut ffi::SCIP_HEUR, + ) -> ffi::SCIP_Retcode { + let data_ptr = unsafe { ffi::SCIPheurGetData(heur) }; + assert!(!data_ptr.is_null()); + drop(unsafe { Box::from_raw(data_ptr as *mut Box) }); + Retcode::Okay.into() + } + + let ptr = Box::into_raw(Box::new(heur)); + let heur_faker = ptr as *mut ffi::SCIP_HEURDATA; + + scip_call!(ffi::SCIPincludeHeur( + self.raw, + c_name.as_ptr(), + c_desc.as_ptr(), + dispchar as ::std::os::raw::c_char, + priority, + freq, + freqofs, + maxdepth, + timing.into(), + usessubscip.into(), + None, + Some(heurfree), + None, + None, + None, + None, + Some(heurexec), + heur_faker, + )); + + Ok(()) + } + + pub(crate) fn add_cons_coef( + &mut self, + cons: Rc, + var: Rc, + coef: f64, + ) -> Result<(), Retcode> { + let cons_is_transformed = unsafe { ffi::SCIPconsIsTransformed(cons.raw) } == 1; + let var_is_transformed = unsafe { ffi::SCIPvarIsTransformed(var.raw) } == 1; + let cons_ptr = if !cons_is_transformed && var_is_transformed { + let mut transformed_cons = MaybeUninit::<*mut ffi::SCIP_Cons>::uninit(); + scip_call!(ffi::SCIPgetTransformedCons( + self.raw, + cons.raw, + transformed_cons.as_mut_ptr() + )); + unsafe { transformed_cons.assume_init() } + } else { + cons.raw + }; + + let var_ptr = if cons_is_transformed && !var_is_transformed { + let mut transformed_var = MaybeUninit::<*mut ffi::SCIP_Var>::uninit(); + scip_call!(ffi::SCIPgetTransformedVar( + self.raw, + var.raw, + transformed_var.as_mut_ptr() + )); + unsafe { transformed_var.assume_init() } + } else { + var.raw + }; + + scip_call! { ffi::SCIPaddCoefLinear(self.raw, cons_ptr, var_ptr, coef) }; + Ok(()) + } + + pub(crate) fn set_cons_modifiable( + &mut self, + cons: Rc, + modifiable: bool, + ) -> Result<(), Retcode> { + scip_call!(ffi::SCIPsetConsModifiable( + self.raw, + cons.raw, + modifiable.into() + )); + Ok(()) + } + + pub(crate) fn n_nodes(&self) -> usize { + unsafe { ffi::SCIPgetNNodes(self.raw) as usize } + } + + pub(crate) fn solving_time(&self) -> f64 { + unsafe { ffi::SCIPgetSolvingTime(self.raw) } + } + + pub(crate) fn n_lp_iterations(&self) -> usize { + unsafe { ffi::SCIPgetNLPIterations(self.raw) as usize } + } + + pub(crate) fn focus_node(&self) -> Node { + Node { + raw: unsafe { ffi::SCIPgetFocusNode(self.raw) }, + } + } + + pub(crate) fn create_child(&mut self) -> Result { + let mut node_ptr = MaybeUninit::uninit(); + scip_call!(ffi::SCIPcreateChild( + self.raw, + node_ptr.as_mut_ptr(), + 0., + ffi::SCIPgetLocalTransEstimate(self.raw), // TODO: pass that as an argument + )); + + let node_ptr = unsafe { node_ptr.assume_init() }; + Ok(Node { raw: node_ptr }) + } + + pub(crate) fn add_sol(&self, sol: Solution) -> Result { + let mut stored = MaybeUninit::uninit(); + scip_call!(ffi::SCIPaddSol(self.raw, sol.raw, stored.as_mut_ptr())); + let stored = unsafe { stored.assume_init() }; + Ok(stored != 0) + } +} + +impl Drop for ScipPtr { + fn drop(&mut self) { + if self.consumed { + return; + } + // Rust Model struct keeps at most one copy of each variable and constraint pointers + // so we need to release them before freeing the SCIP instance + + // first check if we are in a stage where we have variables and constraints + let scip_stage = unsafe { ffi::SCIPgetStage(self.raw) }; + if scip_stage == ffi::SCIP_Stage_SCIP_STAGE_PROBLEM + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_TRANSFORMED + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_INITPRESOLVE + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_PRESOLVING + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_EXITPRESOLVE + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_PRESOLVED + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_INITSOLVE + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_SOLVING + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_SOLVED + || scip_stage == ffi::SCIP_Stage_SCIP_STAGE_EXITSOLVE + { + // release original variables + let n_vars = unsafe { ffi::SCIPgetNOrigVars(self.raw) }; + let vars = unsafe { ffi::SCIPgetOrigVars(self.raw) }; + for i in 0..n_vars { + let mut var = unsafe { *vars.add(i as usize) }; + scip_call_panic!(ffi::SCIPreleaseVar(self.raw, &mut var)); + } + + // release constraints + let n_conss = unsafe { ffi::SCIPgetNOrigConss(self.raw) }; + let conss = unsafe { ffi::SCIPgetOrigConss(self.raw) }; + for i in 0..n_conss { + let mut cons = unsafe { *conss.add(i as usize) }; + scip_call_panic!(ffi::SCIPreleaseCons(self.raw, &mut cons)); + } + } + + // free SCIP instance + unsafe { ffi::SCIPfree(&mut self.raw) }; + } +} diff --git a/src/solution.rs b/src/solution.rs index 389c9f0..9d1f714 100644 --- a/src/solution.rs +++ b/src/solution.rs @@ -5,6 +5,7 @@ use crate::variable::Variable; use crate::{ffi, scip_call_panic}; /// A wrapper for a SCIP solution. +#[derive(PartialEq, Eq)] pub struct Solution { pub(crate) scip_ptr: *mut ffi::SCIP, pub(crate) raw: *mut ffi::SCIP_SOL, @@ -50,19 +51,19 @@ impl fmt::Debug for Solution { } /// Represents and error that can occur when adding a solution. +#[derive(Debug, PartialEq, Eq)] pub enum SolError { /// The solution is infeasible. - Infeasible + Infeasible, } - #[cfg(test)] mod tests { use crate::*; #[test] fn sol_methods() { - let mut model = Model::new() + let model = Model::new() .hide_output() .include_default_plugins() .read_prob("data/test/simple.lp") @@ -86,4 +87,4 @@ mod tests { assert_eq!(sol.obj_val(), model.obj_val()); } -} \ No newline at end of file +} From a331abde81876a5f361e11b8aa965ac8b453fad5 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Thu, 27 Jul 2023 11:00:51 +0200 Subject: [PATCH 5/6] Solving state, represents methods accessible from plugin implementations (#97) --- CHANGELOG.md | 1 + README.md | 1 + src/branchrule.rs | 5 +- src/heuristic.rs | 148 +++++----- src/lib.rs | 1 + src/model.rs | 709 ++++++++++++++++++++++++++++------------------ src/node.rs | 4 +- src/pricer.rs | 4 +- 8 files changed, 519 insertions(+), 354 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8f1ab..31e4ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## unreleased ### Added - Primal heuristic plugin +- Solving Model state, to represent methods accessible when during solving. ### Fixed ### Changed - Moved ScipPtr struct and methods to its own module. diff --git a/README.md b/README.md index 89dd4e2..b6085ca 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ use russcip::status::Status; use russcip::variable::VarType; use russcip::retcode::Retcode; use crate::russcip::model::ModelWithProblem; +use crate::russcip::model::ProblemOrSolving; fn main() { // Create model diff --git a/src/branchrule.rs b/src/branchrule.rs index d8e6f30..2771167 100644 --- a/src/branchrule.rs +++ b/src/branchrule.rs @@ -56,6 +56,7 @@ pub struct BranchingCandidate { mod tests { use super::*; use crate::model::{ModelWithProblem, ProblemCreated}; + use crate::Solving; use crate::{model::Model, status::Status}; struct PanickingBranchingRule; @@ -135,7 +136,7 @@ mod tests { } struct FirstBranchingRule { - model: Model, + model: Model, } impl BranchRule for FirstBranchingRule { @@ -166,7 +167,7 @@ mod tests { } struct CustomBranchingRule { - model: Model, + model: Model, } impl BranchRule for CustomBranchingRule { diff --git a/src/heuristic.rs b/src/heuristic.rs index 6fe3275..7e0d395 100644 --- a/src/heuristic.rs +++ b/src/heuristic.rs @@ -1,7 +1,6 @@ use std::ops::{BitOr, BitOrAssign}; use crate::ffi; -use crate::Solution; /// A trait for defining custom primal heuristics. pub trait Heuristic { @@ -99,10 +98,9 @@ impl From for u32 { } } - #[cfg(test)] mod tests { - use crate::{Model, ModelWithProblem, ProblemCreated, SolError}; + use crate::{Model, ModelWithProblem, ProblemOrSolving, SolError, Solving}; use super::*; @@ -125,19 +123,20 @@ mod tests { let heur = NoSolutionFoundHeur; let mut timing = HeurTiming::BEFORE_PRESOL; timing |= HeurTiming::AFTER_PROP_LOOP; - model.include_heur( - "no_sol_found_heur", - "", - 9999999, - 'n', - 1, - 0, - -1, - timing, - false, - Box::new(heur), - ).solve(); - + model + .include_heur( + "no_sol_found_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + timing, + false, + Box::new(heur), + ) + .solve(); } struct ImpostorHeur; @@ -151,28 +150,29 @@ mod tests { #[test] #[should_panic] fn impostor_heur() { - let model = Model::new() + let model = Model::new() .hide_output() .include_default_plugins() .read_prob("data/test/simple.lp") .unwrap(); let heur = ImpostorHeur; - model.include_heur( - "impostor_heur", - "", - 9999999, - 'n', - 1, - 0, - -1, - HeurTiming::BEFORE_NODE | HeurTiming::AFTER_LP_NODE, - false, - Box::new(heur), - ).solve(); + model + .include_heur( + "impostor_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE | HeurTiming::AFTER_LP_NODE, + false, + Box::new(heur), + ) + .solve(); } - struct DelayedHeur; impl Heuristic for DelayedHeur { @@ -181,7 +181,6 @@ mod tests { } } - #[test] fn delayed_heur() { let model = Model::new() @@ -191,21 +190,22 @@ mod tests { .unwrap(); let heur = DelayedHeur; - model.include_heur( - "delayed_heur", - "", - 9999999, - 'n', - 1, - 0, - -1, - HeurTiming::BEFORE_NODE, - false, - Box::new(heur), - ).solve(); + model + .include_heur( + "delayed_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE, + false, + Box::new(heur), + ) + .solve(); } - struct DidNotRunHeur; impl Heuristic for DidNotRunHeur { @@ -223,23 +223,24 @@ mod tests { .unwrap(); let heur = DidNotRunHeur; - model.include_heur( - "did_not_run_heur", - "", - 9999999, - 'n', - 1, - 0, - -1, - HeurTiming::BEFORE_NODE, - false, - Box::new(heur), - ).solve(); + model + .include_heur( + "did_not_run_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE, + false, + Box::new(heur), + ) + .solve(); } - struct FoundSolHeur { - model: Model, + model: Model, } impl Heuristic for FoundSolHeur { @@ -254,7 +255,6 @@ mod tests { } } - #[test] fn found_sol_heur() { let model = Model::new() @@ -263,18 +263,22 @@ mod tests { .read_prob("data/test/simple.lp") .unwrap(); - let heur = FoundSolHeur { model: model.clone_for_plugins() }; - model.include_heur( - "found_sol_heur", - "", - 9999999, - 'n', - 1, - 0, - -1, - HeurTiming::BEFORE_NODE, - false, - Box::new(heur), - ).solve(); + let heur = FoundSolHeur { + model: model.clone_for_plugins(), + }; + model + .include_heur( + "found_sol_heur", + "", + 9999999, + 'n', + 1, + 0, + -1, + HeurTiming::BEFORE_NODE, + false, + Box::new(heur), + ) + .solve(); } } diff --git a/src/lib.rs b/src/lib.rs index d1b04b4..77099b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ //! use russcip::model::ObjSense; //! use russcip::status::Status; //! use russcip::variable::VarType; +//! use crate::russcip::model::ProblemOrSolving; //! use crate::russcip::model::ModelWithProblem; //! //! diff --git a/src/model.rs b/src/model.rs index d910663..e7a470a 100644 --- a/src/model.rs +++ b/src/model.rs @@ -37,12 +37,18 @@ pub struct ProblemCreated { pub(crate) conss: Rc>>>, } +/// Represents the state of an optimization model during the solving process (to be used in plugins). +#[derive(Debug)] +pub struct Solving { + pub(crate) vars: Rc>>>, + pub(crate) conss: Rc>>>, +} + /// Represents the state of an optimization model that has been solved. #[derive(Debug)] pub struct Solved { pub(crate) vars: Rc>>>, pub(crate) conss: Rc>>>, - pub(crate) best_sol: Option, } impl Model { @@ -185,10 +191,13 @@ impl Model { /// Returns a clone of the current model. /// The clone is meant for use in implementing custom plugins. - pub fn clone_for_plugins(&self) -> Self { + pub fn clone_for_plugins(&self) -> Model { Model { scip: self.scip.clone(), - state: self.state.clone(), + state: Solving { + vars: self.state.vars.clone(), + conss: self.state.conss.clone(), + }, } } @@ -199,26 +208,6 @@ impl Model { .expect("Failed to set constraint modifiable"); } - /// Returns the current node of the model. - /// - /// # Panics - /// - /// This method panics if not called in the `Solving` state, it should only be used from plugins implementations. - pub fn focus_node(&self) -> Node { - self.scip.focus_node() - } - - /// Creates a new child node of the current node and returns it. - /// - /// # Panics - /// - /// This method panics if not called from plugins implementations. - pub fn create_child(&mut self) -> Node { - self.scip - .create_child() - .expect("Failed to create child node in state ProblemCreated") - } - /// Adds a new variable to the model with the given lower bound, upper bound, objective coefficient, name, and type. /// /// # Arguments @@ -254,233 +243,6 @@ impl Model { var } - /// Adds a new priced variable to the SCIP data structure. - /// - /// # Arguments - /// - /// * `lb` - The lower bound of the variable. - /// * `ub` - The upper bound of the variable. - /// * `obj` - The objective function coefficient for the variable. - /// * `name` - The name of the variable. This should be a unique identifier. - /// * `var_type` - The type of the variable, specified as an instance of the `VarType` enum. - /// - /// # Returns - /// - /// This function returns a reference-counted smart pointer (`Rc`) to the created `Variable` instance. - pub fn add_priced_var( - &mut self, - lb: f64, - ub: f64, - obj: f64, - name: &str, - var_type: VarType, - ) -> Rc { - let var = self - .scip - .create_priced_var(lb, ub, obj, name, var_type) - .expect("Failed to create variable in state ProblemCreated"); - let var = Rc::new(var); - let var_id = var.index(); - self.state.vars.borrow_mut().insert(var_id, var.clone()); - var - } - - /// Adds a new constraint to the model with the given variables, coefficients, left-hand side, right-hand side, and name. - /// - /// # Arguments - /// - /// * `vars` - The variables in the constraint. - /// * `coefs` - The coefficients of the variables in the constraint. - /// * `lhs` - The left-hand side of 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. - pub fn add_cons( - &mut self, - vars: Vec>, - coefs: &[f64], - lhs: f64, - rhs: f64, - name: &str, - ) -> Rc { - assert_eq!(vars.len(), coefs.len()); - let cons = self - .scip - .create_cons(vars, coefs, lhs, rhs, name) - .expect("Failed to create constraint in state ProblemCreated"); - let cons = Rc::new(cons); - self.state.conss.borrow_mut().push(cons.clone()); - cons - } - - /// Adds a new set partitioning constraint to the model with the given variables and name. - /// - /// # Arguments - /// - /// * `vars` - The binary variables in 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, or if any of the variables are not binary. - pub fn add_cons_set_part(&mut self, vars: Vec>, name: &str) -> Rc { - assert!(vars.iter().all(|v| v.var_type() == VarType::Binary)); - let cons = self - .scip - .create_cons_set_part(vars, name) - .expect("Failed to add constraint set partition in state ProblemCreated"); - let cons = Rc::new(cons); - self.state.conss.borrow_mut().push(cons.clone()); - cons - } - - /// Adds a new set cover constraint to the model with the given variables and name. - /// - /// # Arguments - /// - /// * `vars` - The binary variables in 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, or if any of the variables are not binary. - pub fn add_cons_set_cover(&mut self, vars: Vec>, name: &str) -> Rc { - assert!(vars.iter().all(|v| v.var_type() == VarType::Binary)); - let cons = self - .scip - .create_cons_set_cover(vars, name) - .expect("Failed to add constraint set cover in state ProblemCreated"); - let cons = Rc::new(cons); - self.state.conss.borrow_mut().push(cons.clone()); - cons - } - - /// Adds a new set packing constraint to the model with the given variables and name. - /// - /// # Arguments - /// - /// * `vars` - The binary variables in 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, or if any of the variables are not binary. - pub fn add_cons_set_pack(&mut self, vars: Vec>, name: &str) -> Rc { - assert!(vars.iter().all(|v| v.var_type() == VarType::Binary)); - let cons = self - .scip - .create_cons_set_pack(vars, name) - .expect("Failed to add constraint set packing in state ProblemCreated"); - let cons = Rc::new(cons); - self.state.conss.borrow_mut().push(cons.clone()); - cons - } - - /// Adds a new quadratic constraint to the model with the given variables, coefficients, left-hand side, right-hand side, and name. - /// - /// # Arguments - /// - /// * `lin_vars` - The linear variables in the constraint. - /// * `lin_coefs` - The coefficients of the linear variables in the constraint. - /// * `quad_vars_1` - The first variable in the quadratic constraints. - /// * `quad_vars_2` - The second variable in the quadratic constraints. - /// * `quad_coefs` - The coefficients of the quadratic terms in the constraint. - /// * `lhs` - The left-hand side of 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. - pub fn add_cons_quadratic( - &mut self, - lin_vars: Vec>, - lin_coefs: &mut [f64], - quad_vars_1: Vec>, - quad_vars_2: Vec>, - quad_coefs: &mut [f64], - lhs: f64, - rhs: f64, - name: &str, - ) -> Rc { - assert_eq!(lin_vars.len(), lin_coefs.len()); - assert_eq!(quad_vars_1.len(), quad_vars_2.len()); - assert_eq!(quad_vars_1.len(), quad_coefs.len()); - let cons = self - .scip - .create_cons_quadratic( - lin_vars, - lin_coefs, - quad_vars_1, - quad_vars_2, - quad_coefs, - lhs, - rhs, - name, - ) - .expect("Failed to create constraint in state ProblemCreated"); - let cons = Rc::new(cons); - self.state.conss.borrow_mut().push(cons.clone()); - cons - } - - /// Adds a coefficient to the given constraint for the given variable and coefficient value. - /// - /// # Arguments - /// - /// * `cons` - The constraint to add the coefficient to. - /// * `var` - The variable to add the coefficient for. - /// * `coef` - The coefficient value to add. - /// - /// # Panics - /// - /// This method panics if the coefficient cannot be added in the current state. - pub fn add_cons_coef(&mut self, cons: Rc, var: Rc, coef: f64) { - self.scip - .add_cons_coef(cons, var, coef) - .expect("Failed to add constraint coefficient in state ProblemCreated"); - } - - /// Adds a binary variable to the given set partitioning constraint. - /// - /// # Arguments - /// - /// * `cons` - The constraint to add the variable to. - /// * `var` - The binary variable to add. - /// - /// # Panics - /// - /// This method panics if the variable cannot be added in the current state, or if the variable is not binary. - pub fn add_cons_coef_setppc(&mut self, cons: Rc, var: Rc) { - assert_eq!(var.var_type(), VarType::Binary); - self.scip - .add_cons_coef_setppc(cons, var) - .expect("Failed to add constraint coefficient in state ProblemCreated"); - } - /// Includes a new branch rule in the model with the given name, description, priority, maximum depth, maximum bound distance, and implementation. /// /// # Arguments @@ -624,50 +386,78 @@ impl Model { self.scip .solve() .expect("Failed to solve problem in state ProblemCreated"); - let mut new_model = Model { + let new_model = Model { scip: self.scip, state: Solved { vars: self.state.vars, conss: self.state.conss, - best_sol: None, }, }; - new_model._set_best_sol(); new_model } +} - /// Creates a new solution initialized to zero. - pub fn create_sol(&mut self) -> Solution { +impl Model { + /// Returns the current node of the model. + /// + /// # Panics + /// + /// This method panics if not called in the `Solving` state, it should only be used from plugins implementations. + pub fn focus_node(&self) -> Node { + self.scip.focus_node() + } + + /// Creates a new child node of the current node and returns it. + /// + /// # Panics + /// + /// This method panics if not called from plugins implementations. + pub fn create_child(&mut self) -> Node { self.scip - .create_sol() - .expect("Failed to create solution in state ProblemCreated") + .create_child() + .expect("Failed to create child node in state ProblemCreated") } - /// Adds a solution to the model + /// Adds a new priced variable to the SCIP data structure. + /// + /// # Arguments + /// + /// * `lb` - The lower bound of the variable. + /// * `ub` - The upper bound of the variable. + /// * `obj` - The objective function coefficient for the variable. + /// * `name` - The name of the variable. This should be a unique identifier. + /// * `var_type` - The type of the variable, specified as an instance of the `VarType` enum. /// /// # Returns - /// A `Result` indicating whether the solution was added successfully. - pub fn add_sol(&self, sol: Solution) -> Result<(), SolError> { - let succesfully_stored = self.scip.add_sol(sol).expect("Failed to add solution"); - if succesfully_stored { - Ok(()) - } else { - Err(SolError::Infeasible) - } + /// + /// This function returns a reference-counted smart pointer (`Rc`) to the created `Variable` instance. + pub fn add_priced_var( + &mut self, + lb: f64, + ub: f64, + obj: f64, + name: &str, + var_type: VarType, + ) -> Rc { + let var = self + .scip + .create_priced_var(lb, ub, obj, name, var_type) + .expect("Failed to create variable in state ProblemCreated"); + let var = Rc::new(var); + let var_id = var.index(); + self.state.vars.borrow_mut().insert(var_id, var.clone()); + var } } impl Model { - /// Sets the best solution for the optimization model if one exists. - fn _set_best_sol(&mut self) { - if self.scip.n_sols() > 0 { - self.state.best_sol = Some(self.scip.best_sol()); - } - } - /// Returns the best solution for the optimization model, if one exists. - pub fn best_sol(&self) -> Option> { - self.state.best_sol.as_ref().map(Box::new) + pub fn best_sol(&self) -> Option { + if self.n_sols() > 0 { + Some(self.scip.best_sol()) + } else { + None + } } /// Returns the number of solutions found by the optimization model. @@ -756,7 +546,374 @@ macro_rules! impl_ModelWithProblem { } } -impl_ModelWithProblem!(for Model, Model); +impl_ModelWithProblem!(for Model, Model, Model); + +/// A trait for optimization models with a problem created or solved. +pub trait ProblemOrSolving { + /// Creates a new solution initialized to zero. + fn create_sol(&mut self) -> Solution; + + /// Adds a solution to the model + /// + /// # Returns + /// A `Result` indicating whether the solution was added successfully. + fn add_sol(&self, sol: Solution) -> Result<(), SolError>; + + /// Adds a binary variable to the given set partitioning constraint. + /// + /// # Arguments + /// + /// * `cons` - The constraint to add the variable to. + /// * `var` - The binary variable to add. + /// + /// # Panics + /// + /// This method panics if the variable cannot be added in the current state, or if the variable is not binary. + fn add_cons_coef_setppc(&mut self, cons: Rc, var: Rc); + + /// Adds a coefficient to the given constraint for the given variable and coefficient value. + /// + /// # Arguments + /// + /// * `cons` - The constraint to add the coefficient to. + /// * `var` - The variable to add the coefficient for. + /// * `coef` - The coefficient value to add. + /// + /// # Panics + /// + /// This method panics if the coefficient cannot be added in the current state. + fn add_cons_coef(&mut self, cons: Rc, var: Rc, coef: f64); + /// Adds a new quadratic constraint to the model with the given variables, coefficients, left-hand side, right-hand side, and name. + /// + /// # Arguments + /// + /// * `lin_vars` - The linear variables in the constraint. + /// * `lin_coefs` - The coefficients of the linear variables in the constraint. + /// * `quad_vars_1` - The first variable in the quadratic constraints. + /// * `quad_vars_2` - The second variable in the quadratic constraints. + /// * `quad_coefs` - The coefficients of the quadratic terms in the constraint. + /// * `lhs` - The left-hand side of 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_quadratic( + &mut self, + lin_vars: Vec>, + lin_coefs: &mut [f64], + quad_vars_1: Vec>, + quad_vars_2: Vec>, + quad_coefs: &mut [f64], + lhs: f64, + rhs: f64, + name: &str, + ) -> Rc; + /// Adds a new constraint to the model with the given variables, coefficients, left-hand side, right-hand side, and name. + /// + /// # Arguments + /// + /// * `vars` - The variables in the constraint. + /// * `coefs` - The coefficients of the variables in the constraint. + /// * `lhs` - The left-hand side of 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( + &mut self, + vars: Vec>, + coefs: &[f64], + lhs: f64, + rhs: f64, + name: &str, + ) -> Rc; + /// Adds a new set partitioning constraint to the model with the given variables and name. + /// + /// # Arguments + /// + /// * `vars` - The binary variables in 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, or if any of the variables are not binary. + fn add_cons_set_part(&mut self, vars: Vec>, name: &str) -> Rc; + /// Adds a new set cover constraint to the model with the given variables and name. + /// + /// # Arguments + /// + /// * `vars` - The binary variables in 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, or if any of the variables are not binary. + fn add_cons_set_cover(&mut self, vars: Vec>, name: &str) -> Rc; + /// Adds a new set packing constraint to the model with the given variables and name. + /// + /// # Arguments + /// + /// * `vars` - The binary variables in 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, or if any of the variables are not binary. + fn add_cons_set_pack(&mut self, vars: Vec>, name: &str) -> Rc; +} + +macro_rules! impl_ProblemOrSolving { + (for $($t:ty),+) => { + $(impl ProblemOrSolving for $t { + + /// Creates a new solution initialized to zero. + fn create_sol(&mut self) -> Solution { + self.scip + .create_sol() + .expect("Failed to create solution in state ProblemCreated") + } + + /// Adds a solution to the model + /// + /// # Returns + /// A `Result` indicating whether the solution was added successfully. + fn add_sol(&self, sol: Solution) -> Result<(), SolError> { + let succesfully_stored = self.scip.add_sol(sol).expect("Failed to add solution"); + if succesfully_stored { + Ok(()) + } else { + Err(SolError::Infeasible) + } + } + + /// Adds a binary variable to the given set partitioning constraint. + /// + /// # Arguments + /// + /// * `cons` - The constraint to add the variable to. + /// * `var` - The binary variable to add. + /// + /// # Panics + /// + /// This method panics if the variable cannot be added in the current state, or if the variable is not binary. + fn add_cons_coef_setppc(&mut self, cons: Rc, var: Rc) { + assert_eq!(var.var_type(), VarType::Binary); + self.scip + .add_cons_coef_setppc(cons, var) + .expect("Failed to add constraint coefficient in state ProblemCreated"); + } + + + /// Adds a coefficient to the given constraint for the given variable and coefficient value. + /// + /// # Arguments + /// + /// * `cons` - The constraint to add the coefficient to. + /// * `var` - The variable to add the coefficient for. + /// * `coef` - The coefficient value to add. + /// + /// # Panics + /// + /// This method panics if the coefficient cannot be added in the current state. + fn add_cons_coef(&mut self, cons: Rc, var: Rc, coef: f64) { + self.scip + .add_cons_coef(cons, var, coef) + .expect("Failed to add constraint coefficient in state ProblemCreated"); + } + + /// Adds a new quadratic constraint to the model with the given variables, coefficients, left-hand side, right-hand side, and name. + /// + /// # Arguments + /// + /// * `lin_vars` - The linear variables in the constraint. + /// * `lin_coefs` - The coefficients of the linear variables in the constraint. + /// * `quad_vars_1` - The first variable in the quadratic constraints. + /// * `quad_vars_2` - The second variable in the quadratic constraints. + /// * `quad_coefs` - The coefficients of the quadratic terms in the constraint. + /// * `lhs` - The left-hand side of 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_quadratic( + &mut self, + lin_vars: Vec>, + lin_coefs: &mut [f64], + quad_vars_1: Vec>, + quad_vars_2: Vec>, + quad_coefs: &mut [f64], + lhs: f64, + rhs: f64, + name: &str, + ) -> Rc { + assert_eq!(lin_vars.len(), lin_coefs.len()); + assert_eq!(quad_vars_1.len(), quad_vars_2.len()); + assert_eq!(quad_vars_1.len(), quad_coefs.len()); + let cons = self + .scip + .create_cons_quadratic( + lin_vars, + lin_coefs, + quad_vars_1, + quad_vars_2, + quad_coefs, + lhs, + rhs, + name, + ) + .expect("Failed to create constraint in state ProblemCreated"); + let cons = Rc::new(cons); + self.state.conss.borrow_mut().push(cons.clone()); + cons + } + + + + /// Adds a new constraint to the model with the given variables, coefficients, left-hand side, right-hand side, and name. + /// + /// # Arguments + /// + /// * `vars` - The variables in the constraint. + /// * `coefs` - The coefficients of the variables in the constraint. + /// * `lhs` - The left-hand side of 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( + &mut self, + vars: Vec>, + coefs: &[f64], + lhs: f64, + rhs: f64, + name: &str, + ) -> Rc { + assert_eq!(vars.len(), coefs.len()); + let cons = self + .scip + .create_cons(vars, coefs, lhs, rhs, name) + .expect("Failed to create constraint in state ProblemCreated"); + let cons = Rc::new(cons); + self.state.conss.borrow_mut().push(cons.clone()); + cons + } + + /// Adds a new set partitioning constraint to the model with the given variables and name. + /// + /// # Arguments + /// + /// * `vars` - The binary variables in 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, or if any of the variables are not binary. + fn add_cons_set_part(&mut self, vars: Vec>, name: &str) -> Rc { + assert!(vars.iter().all(|v| v.var_type() == VarType::Binary)); + let cons = self + .scip + .create_cons_set_part(vars, name) + .expect("Failed to add constraint set partition in state ProblemCreated"); + let cons = Rc::new(cons); + self.state.conss.borrow_mut().push(cons.clone()); + cons + } + + /// Adds a new set cover constraint to the model with the given variables and name. + /// + /// # Arguments + /// + /// * `vars` - The binary variables in 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, or if any of the variables are not binary. + fn add_cons_set_cover(&mut self, vars: Vec>, name: &str) -> Rc { + assert!(vars.iter().all(|v| v.var_type() == VarType::Binary)); + let cons = self + .scip + .create_cons_set_cover(vars, name) + .expect("Failed to add constraint set cover in state ProblemCreated"); + let cons = Rc::new(cons); + self.state.conss.borrow_mut().push(cons.clone()); + cons + } + + /// Adds a new set packing constraint to the model with the given variables and name. + /// + /// # Arguments + /// + /// * `vars` - The binary variables in 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, or if any of the variables are not binary. + fn add_cons_set_pack(&mut self, vars: Vec>, name: &str) -> Rc { + assert!(vars.iter().all(|v| v.var_type() == VarType::Binary)); + let cons = self + .scip + .create_cons_set_pack(vars, name) + .expect("Failed to add constraint set packing in state ProblemCreated"); + let cons = Rc::new(cons); + self.state.conss.borrow_mut().push(cons.clone()); + cons + } + + })* + } +} + +impl_ProblemOrSolving!(for Model, Model); impl Model { /// Returns a pointer to the underlying SCIP instance. diff --git a/src/node.rs b/src/node.rs index 0637f33..ee38eeb 100644 --- a/src/node.rs +++ b/src/node.rs @@ -42,11 +42,11 @@ impl Node { mod tests { use crate::{ branchrule::{BranchRule, BranchingResult}, - model::{Model, ProblemCreated}, + model::{Model, Solving}, }; struct NodeDataBranchRule { - model: Model, + model: Model, } impl BranchRule for NodeDataBranchRule { diff --git a/src/pricer.rs b/src/pricer.rs index 16e13b2..17aec87 100644 --- a/src/pricer.rs +++ b/src/pricer.rs @@ -48,7 +48,7 @@ mod tests { use crate::{ model::{Model, ModelWithProblem, ProblemCreated}, status::Status, - variable::VarType, + variable::VarType, Solving, ProblemOrSolving, }; struct PanickingPricer; @@ -163,7 +163,7 @@ mod tests { struct AddSameColumnPricer { added: bool, - model: Model, + model: Model, data: ComplexData, } From 248aebff33f5e0e19362020cb0165bf3d001a3fe Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Thu, 27 Jul 2023 11:24:17 +0200 Subject: [PATCH 6/6] Separate solution methods to its own trait (#98) * Solving state, represents methods accessible from plugin implementations * Separate solution methods to its own trait --- README.md | 1 + src/lib.rs | 2 +- src/model.rs | 47 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b6085ca..67aa5af 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ use russcip::variable::VarType; use russcip::retcode::Retcode; use crate::russcip::model::ModelWithProblem; use crate::russcip::model::ProblemOrSolving; +use crate::russcip::WithSolutions; fn main() { // Create model diff --git a/src/lib.rs b/src/lib.rs index 77099b0..4487beb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ //! use russcip::variable::VarType; //! use crate::russcip::model::ProblemOrSolving; //! use crate::russcip::model::ModelWithProblem; -//! +//! use crate::russcip::WithSolutions; //! //! // Create model //! let mut model = Model::new() diff --git a/src/model.rs b/src/model.rs index e7a470a..d70cc87 100644 --- a/src/model.rs +++ b/src/model.rs @@ -451,20 +451,6 @@ impl Model { } impl Model { - /// Returns the best solution for the optimization model, if one exists. - pub fn best_sol(&self) -> Option { - if self.n_sols() > 0 { - Some(self.scip.best_sol()) - } else { - None - } - } - - /// Returns the number of solutions found by the optimization model. - pub fn n_sols(&self) -> usize { - self.scip.n_sols() - } - /// Returns the objective value of the best solution found by the optimization model. pub fn obj_val(&self) -> f64 { self.scip.obj_val() @@ -915,6 +901,39 @@ macro_rules! impl_ProblemOrSolving { impl_ProblemOrSolving!(for Model, Model); +/// A trait for optimization models with any state that might have solutions. +pub trait WithSolutions { + /// Returns the best solution for the optimization model, if one exists. + fn best_sol(&self) -> Option; + + /// Returns the number of solutions found by the optimization model. + fn n_sols(&self) -> usize; +} + +macro_rules! impl_WithSolutions { + (for $($t:ty),+) => { + $(impl WithSolutions for $t { + + /// Returns the best solution for the optimization model, if one exists. + fn best_sol(&self) -> Option { + if self.n_sols() > 0 { + Some(self.scip.best_sol()) + } else { + None + } + } + + /// Returns the number of solutions found by the optimization model. + fn n_sols(&self) -> usize { + self.scip.n_sols() + } + + })* + } +} + +impl_WithSolutions!(for Model, Model, Model); + impl Model { /// Returns a pointer to the underlying SCIP instance. ///