Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move ScipPtr struct and methods to its own module #96

Merged
merged 13 commits into from
Jul 27, 2023
4 changes: 2 additions & 2 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
coverage:
range: 70..100
range: 90..100
round: nearest
precision: 2
precision: 2
4 changes: 2 additions & 2 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
# cargo test
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

## unreleased
### Added
- Primal heuristic plugin
### Fixed
### Changed
- Moved ScipPtr struct and methods to its own module.
### Removed

## 0.2.4
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions src/eventhdlr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
}

Expand Down
280 changes: 280 additions & 0 deletions src/heuristic.rs
Original file line number Diff line number Diff line change
@@ -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<HeurTiming> for u32 {
fn from(mask: HeurTiming) -> Self {
mask.0 as u32
}
}

impl From<u32> for HeurTiming {
fn from(mask: u32) -> Self {
HeurTiming(mask as u64)
}
}

impl From<HeurResult> 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<ProblemCreated>,
}

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();
}
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading