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

Basic structure for separator plugins #174

Merged
merged 5 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub use eventhdlr::*;
pub mod heuristic;
pub use heuristic::*;

/// Contains the `Separator` trait used to define custom separation routines.
pub mod separator;
pub use separator::*;

/// Contains all the traits and structs that are re-exported by default.
pub mod prelude;

Expand Down
44 changes: 43 additions & 1 deletion src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ use std::rc::{Rc, Weak};

use crate::constraint::Constraint;
use crate::eventhdlr::Eventhdlr;
use crate::ffi;
use crate::node::Node;
use crate::retcode::Retcode;
use crate::scip::ScipPtr;
use crate::solution::{SolError, Solution};
use crate::status::Status;
use crate::variable::{VarId, VarType, Variable};
use crate::{ffi, Separator};
use crate::{BranchRule, HeurTiming, Heuristic, Pricer};

/// Represents an optimization model.
Expand Down Expand Up @@ -268,6 +268,48 @@ impl Model<ProblemCreated> {
self
}

/// Includes a new separator in the model.
///
/// # Arguments
///
/// * `name` - The name of the separator. This should be a unique identifier.
/// * `desc` - A brief description of the separator. This is used for informational purposes.
/// * `priority` - The priority of the separator. When SCIP decides which separator to call, it considers their priorities. A higher value indicates a higher priority.
/// * `freq` - The frequency for calling the separator in the tree; 1 means at every node, 2 means at every other node and so on, -1 turns off the separator.
/// * `maxbounddist` - The maximum relative distance from the current node's dual bound to primal bound compared to the best node's dual bound for applying the separator. A value of 0.0 means the separator can only be applied on the current best node, while 1.0 means it can be applied on all nodes.
/// * `usesubscip` - Does the separator use a secondary SCIP instance?
/// * `delay` - A boolean indicating whether the separator should be delayed.
/// * `separator`- The separator to be included. This should be a mutable reference to an object that implements the `Separator` trait, and represents the separator data.
///
/// # Returns
///
/// This function returns the `Model` instance for which the separator was included. This allows for method chaining.
pub fn include_separator(
self,
name: &str,
desc: &str,
priority: i32,
freq: i32,
maxbounddist: f64,
usesubscip: bool,
delay: bool,
separator: Box<dyn Separator>,
) -> Self {
self.scip
.include_separator(
name,
desc,
priority,
freq,
maxbounddist,
usesubscip,
delay,
separator,
)
.expect("Failed to include separator at state ProblemCreated");
self
}

/// Includes a new event handler in the model.
///
/// # Arguments
Expand Down
81 changes: 80 additions & 1 deletion src/scip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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,
ParamSetting, Retcode, Separator, Solution, Status, VarType, Variable,
};
use crate::{scip_call, HeurTiming, Heuristic};
use core::panic;
Expand Down Expand Up @@ -920,6 +920,85 @@ impl ScipPtr {
Ok(())
}

pub(crate) fn include_separator(
&self,
name: &str,
desc: &str,
priority: i32,
freq: i32,
maxbounddist: f64,
usesubscip: bool,
delay: bool,
separator: Box<dyn Separator>,
) -> Result<(), Retcode> {
let c_name = CString::new(name).unwrap();
let c_desc = CString::new(desc).unwrap();

extern "C" fn sepexeclp(
_scip: *mut ffi::SCIP,
separator: *mut ffi::SCIP_SEPA,
result: *mut ffi::SCIP_RESULT,
_allowlocal: ::std::os::raw::c_uint,
_depth: ::std::os::raw::c_int,
) -> ffi::SCIP_Retcode {
let data_ptr = unsafe { ffi::SCIPsepaGetData(separator) };
assert!(!data_ptr.is_null());
let rule_ptr = data_ptr as *mut Box<dyn Separator>;

let sep_res = unsafe { (*rule_ptr).execute_lp() };

unsafe { *result = sep_res.into() };

Retcode::Okay.into()
}

extern "C" fn sepexecsol(
_scip: *mut ffi::SCIP,
_separator: *mut ffi::SCIP_SEPA,
_sol: *mut SCIP_SOL,
_result: *mut ffi::SCIP_RESULT,
_allowlocal: ::std::os::raw::c_uint,
_depth: ::std::os::raw::c_int,
) -> ffi::SCIP_Retcode {
Retcode::Okay.into()
}

extern "C" fn sepfree(
_scip: *mut ffi::SCIP,
separator: *mut ffi::SCIP_SEPA,
) -> ffi::SCIP_Retcode {
let data_ptr = unsafe { ffi::SCIPsepaGetData(separator) };
assert!(!data_ptr.is_null());
drop(unsafe { Box::from_raw(data_ptr as *mut Box<dyn Separator>) });
Retcode::Okay.into()
}

let ptr = Box::into_raw(Box::new(separator));
let sep_faker = ptr as *mut ffi::SCIP_SEPADATA;

scip_call!(ffi::SCIPincludeSepa(
self.raw,
c_name.as_ptr(),
c_desc.as_ptr(),
priority,
freq,
maxbounddist,
usesubscip.into(),
delay.into(),
None,
Some(sepfree),
None,
None,
None,
None,
Some(sepexeclp),
Some(sepexecsol),
sep_faker,
));

Ok(())
}

pub(crate) fn add_cons_coef(
&self,
cons: Rc<Constraint>,
Expand Down
99 changes: 99 additions & 0 deletions src/separator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::ffi;
use scip_sys::SCIP_Result;

/// A trait for defining custom separation routines.
pub trait Separator {
/// Execute the separation routine on LP solutions.
fn execute_lp(&mut self) -> SeparationResult;
}

/// The result of a separation routine.
pub enum SeparationResult {
/// Detected that the node is infeasible in the variable's bounds and can be cut off
Cutoff,
/// Added a constraint to the problem
ConsAdded,
/// Reduced the domain of a variable
ReducedDomain,
/// Added a cutting plane to the LP
Separated,
/// The separator searched, but did not find domain reductions, cutting planes, or cut constraints
DidNotFind,
/// The separator was skipped
DidNotRun,
/// The separator was skipped, but should be called again
Delayed,
/// A new separation round should be started without calling the remaining separator methods
NewRound,
}

impl From<SCIP_Result> for SeparationResult {
fn from(result: SCIP_Result) -> Self {
match result {
ffi::SCIP_Result_SCIP_CUTOFF => SeparationResult::Cutoff,
ffi::SCIP_Result_SCIP_CONSADDED => SeparationResult::ConsAdded,
ffi::SCIP_Result_SCIP_REDUCEDDOM => SeparationResult::ReducedDomain,
ffi::SCIP_Result_SCIP_SEPARATED => SeparationResult::Separated,
ffi::SCIP_Result_SCIP_DIDNOTFIND => SeparationResult::DidNotFind,
ffi::SCIP_Result_SCIP_DIDNOTRUN => SeparationResult::DidNotRun,
ffi::SCIP_Result_SCIP_DELAYED => SeparationResult::Delayed,
ffi::SCIP_Result_SCIP_NEWROUND => SeparationResult::NewRound,
_ => panic!("Unknown SCIP result"),
}
}
}

impl From<SeparationResult> for SCIP_Result {
fn from(val: SeparationResult) -> Self {
match val {
SeparationResult::Cutoff => ffi::SCIP_Result_SCIP_CUTOFF,
SeparationResult::ConsAdded => ffi::SCIP_Result_SCIP_CONSADDED,
SeparationResult::ReducedDomain => ffi::SCIP_Result_SCIP_REDUCEDDOM,
SeparationResult::Separated => ffi::SCIP_Result_SCIP_SEPARATED,
SeparationResult::DidNotFind => ffi::SCIP_Result_SCIP_DIDNOTFIND,
SeparationResult::DidNotRun => ffi::SCIP_Result_SCIP_DIDNOTRUN,
SeparationResult::Delayed => ffi::SCIP_Result_SCIP_DELAYED,
SeparationResult::NewRound => ffi::SCIP_Result_SCIP_NEWROUND,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::Model;

struct NotRunningSeparator;

impl Separator for NotRunningSeparator {
fn execute_lp(&mut self) -> SeparationResult {
SeparationResult::DidNotRun
}
}

#[test]
fn test_not_running_separator() {
let model = Model::new()
.hide_output()
.set_longint_param("limits/nodes", 2)
.unwrap() // only call brancher once
.include_default_plugins()
.read_prob("data/test/gen-ip054.mps")
.unwrap();

let sep = NotRunningSeparator;

model
.include_separator(
"NotRunningSeparator",
"",
1000000,
1,
1.0,
false,
false,
Box::new(sep),
)
.solve();
}
}
Loading