Skip to content

Commit

Permalink
Add access to plugins internal data
Browse files Browse the repository at this point in the history
  • Loading branch information
mmghannam committed Jan 9, 2025
1 parent 9c572b7 commit b87a799
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 29 deletions.
80 changes: 76 additions & 4 deletions src/branchrule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,46 @@ pub struct BranchingCandidate {
pub frac: f64,
}

/// A wrapper struct for the internal ffi::SCIP_BRANCHRULE
pub struct SCIPBranchRule {
pub(crate) raw: *mut ffi::SCIP_BRANCHRULE,
}

impl SCIPBranchRule {
/// Returns the name of the branch rule.
pub fn name(&self) -> String {
unsafe {
let name_ptr = ffi::SCIPbranchruleGetName(self.raw);
let name = std::ffi::CStr::from_ptr(name_ptr).to_str().unwrap();
name.to_string()
}
}

/// Returns the description of the branch rule.
pub fn desc(&self) -> String {
unsafe {
let desc_ptr = ffi::SCIPbranchruleGetDesc(self.raw);
let desc = std::ffi::CStr::from_ptr(desc_ptr).to_str().unwrap();
desc.to_string()
}
}

/// Returns the priority of the branch rule.
pub fn priority(&self) -> i32 {
unsafe { ffi::SCIPbranchruleGetPriority(self.raw) }
}

/// Returns the maxdepth of the branch rule.
pub fn maxdepth(&self) -> i32 {
unsafe { ffi::SCIPbranchruleGetMaxdepth(self.raw) }
}

/// Returns the maxbounddist of the branch rule.
pub fn maxbounddist(&self) -> f64 {
unsafe { ffi::SCIPbranchruleGetMaxbounddist(self.raw) }
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -99,10 +139,6 @@ mod tests {

let solved = model.solve();
assert_eq!(solved.status(), Status::NodeLimit);
// assert!(br.chosen.is_some());
// let candidate = br.chosen.unwrap();
// assert!(candidate.lp_sol_val.fract() > 0.);
// assert!(candidate.frac > 0. && candidate.frac < 1.);
}

struct CuttingOffBranchingRule;
Expand Down Expand Up @@ -238,4 +274,40 @@ mod tests {

assert!(solved.n_nodes() > 1);
}

struct InternalBranchRuleDataTester;

impl BranchRule for InternalBranchRuleDataTester {
fn execute(
&mut self,
_model: Model<Solving>,
_candidates: Vec<BranchingCandidate>,
) -> BranchingResult {
BranchingResult::DidNotRun
}
}

#[test]
fn test_internal_scip_branch_rule() {
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 br = InternalBranchRuleDataTester;

model
.include_branch_rule(
"InternalBranchRuleDataTester",
"Internal branch rule data tester",
1000000,
1,
1.0,
Box::new(br),
)
.solve();
}
}
8 changes: 3 additions & 5 deletions src/col.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,7 @@ impl PartialEq for Col {

#[cfg(test)]
mod tests {
use crate::{
minimal_model, BasisStatus, EventMask, Eventhdlr, Model, ModelWithProblem,
ProblemOrSolving, Solving, VarType,
};
use crate::{minimal_model, BasisStatus, Event, EventMask, Eventhdlr, Model, ModelWithProblem, ProblemOrSolving, SCIPEventhdlr, Solving, VarType};

struct ColTesterEventHandler;

Expand All @@ -204,7 +201,8 @@ mod tests {
EventMask::FIRST_LP_SOLVED
}

fn execute(&mut self, model: Model<Solving>) {
fn execute(&mut self, model: Model<Solving>, _eventhdlr: SCIPEventhdlr, event: Event) {
assert_eq!(event.event_type(), EventMask::FIRST_LP_SOLVED);
let vars = model.vars();
let first_var = vars[0].clone();
let col = first_var.col().unwrap();
Expand Down
71 changes: 67 additions & 4 deletions src/eventhdlr.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Model, Solving};
use crate::{ffi, Model, Solving};
use std::ops::{BitOr, BitOrAssign};

/// Trait used to define custom event handlers.
Expand All @@ -10,11 +10,14 @@ pub trait Eventhdlr {
///
/// # Arguments
/// * `model` - the current model of the SCIP instance in `Solving` stage
fn execute(&mut self, model: Model<Solving>);
/// * `eventhdlr` - the event handler
/// * `event` - the event mask that triggered the event handler
fn execute(&mut self, model: Model<Solving>, eventhdlr: SCIPEventhdlr, event: Event);
}

/// The EventMask represents different states or actions within an optimization problem.
#[derive(Debug, Copy, Clone)]
#[derive(PartialEq)]
pub struct EventMask(u64);

impl EventMask {
Expand Down Expand Up @@ -147,6 +150,11 @@ impl EventMask {
| Self::ROW_DELETED_LP.0
| Self::ROW_CHANGED.0,
);

/// Event mask matches some other mask
pub fn matches(&self, mask: EventMask) -> bool {
self.0 & mask.0 != 0
}
}

impl BitOr for EventMask {
Expand All @@ -169,11 +177,41 @@ impl From<EventMask> for u64 {
}
}

/// Wrapper for the internal SCIP event handler.
pub struct SCIPEventhdlr {
pub(crate) raw: *mut ffi::SCIP_EVENTHDLR,
}

impl SCIPEventhdlr {
/// Returns the name of the event handler.
pub fn name(&self) -> String {
unsafe {
let name = ffi::SCIPeventhdlrGetName(self.raw);
std::ffi::CStr::from_ptr(name)
.to_string_lossy()
.into_owned()
}
}
}

/// Wrapper for the internal SCIP event.
pub struct Event {
pub(crate) raw: *mut ffi::SCIP_EVENT,
}

impl Event {
/// Returns the event type of the event.
pub fn event_type(&self) -> EventMask {
let event_type = unsafe { ffi::SCIPeventGetType(self.raw) };
EventMask(event_type)
}
}

#[cfg(test)]
mod tests {
use crate::eventhdlr::{EventMask, Eventhdlr};
use crate::model::Model;
use crate::Solving;
use crate::{Event, Solving};
use std::cell::RefCell;
use std::rc::Rc;

Expand All @@ -186,7 +224,7 @@ mod tests {
EventMask::LP_EVENT | EventMask::NODE_EVENT
}

fn execute(&mut self, _model: Model<Solving>) {
fn execute(&mut self, _model: Model<Solving>, _eventhdlr: crate::SCIPEventhdlr, _event: Event) {
*self.counter.borrow_mut() += 1;
}
}
Expand All @@ -208,4 +246,29 @@ mod tests {

assert!(*counter.borrow() > 1);
}


struct InternalSCIPEventHdlrTester;

impl Eventhdlr for InternalSCIPEventHdlrTester {
fn get_type(&self) -> EventMask {
EventMask::LP_EVENT | EventMask::NODE_EVENT
}

fn execute(&mut self, _model: Model<Solving>, eventhdlr: crate::SCIPEventhdlr, event: Event) {
assert!(self.get_type().matches(event.event_type()));
assert_eq!(eventhdlr.name(), "InternalSCIPEventHdlrTester");
}
}

#[test]
fn test_internal_eventhdlr() {
Model::new()
.hide_output()
.include_default_plugins()
.read_prob("data/test/simple.lp")
.unwrap()
.include_eventhdlr("InternalSCIPEventHdlrTester", "", Box::new(InternalSCIPEventHdlrTester))
.solve();
}
}
92 changes: 86 additions & 6 deletions src/pricer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ pub trait Pricer {
/// Generates negative reduced cost columns.
///
/// # Arguments
/// * `model` - the current model of the SCIP instance in `Solving` stage
/// * `model`: the current model of the SCIP instance in `Solving` stage.
/// * `pricer`: the internal pricer object.
/// * `farkas`: If true, the pricer should generate columns to repair feasibility of LP.
fn generate_columns(&mut self, model: Model<Solving>, farkas: bool) -> PricerResult;
fn generate_columns(&mut self, model: Model<Solving>, pricer: SCIPPricer, farkas: bool) -> PricerResult;
}

/// An enum representing the possible states of a `PricerResult`.
Expand Down Expand Up @@ -44,6 +45,55 @@ impl From<PricerResultState> for SCIP_Result {
}
}


/// A wrapper around a SCIP pricer object.
pub struct SCIPPricer {
pub(crate) raw: *mut ffi::SCIP_PRICER,
}

impl SCIPPricer {
/// Returns the name of the pricer.
pub fn name(&self) -> String {
unsafe {
let name = ffi::SCIPpricerGetName(self.raw);
std::ffi::CStr::from_ptr(name)
.to_string_lossy()
.into_owned()
}
}

/// Returns the description of the pricer.
pub fn desc(&self) -> String {
unsafe {
let desc = ffi::SCIPpricerGetDesc(self.raw);
std::ffi::CStr::from_ptr(desc)
.to_string_lossy()
.into_owned()
}
}

/// Returns the priority of the pricer.
pub fn priority(&self) -> i32 {
unsafe {
ffi::SCIPpricerGetPriority(self.raw)
}
}

/// Returns the delay of the pricer.
pub fn is_delayed(&self) -> bool {
unsafe {
ffi::SCIPpricerIsDelayed(self.raw) != 0
}
}

/// Returns whether the pricer is active.
pub fn is_active(&self) -> bool {
unsafe {
ffi::SCIPpricerIsActive(self.raw) != 0
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -55,7 +105,7 @@ mod tests {
struct LyingPricer;

impl Pricer for LyingPricer {
fn generate_columns(&mut self, _model: Model<Solving>, _farkas: bool) -> PricerResult {
fn generate_columns(&mut self, _model: Model<Solving>, _pricer: SCIPPricer, _farkas: bool) -> PricerResult {
PricerResult {
state: PricerResultState::FoundColumns,
lower_bound: None,
Expand All @@ -81,7 +131,7 @@ mod tests {
struct EarlyStoppingPricer;

impl Pricer for EarlyStoppingPricer {
fn generate_columns(&mut self, _model: Model<Solving>, _farkas: bool) -> PricerResult {
fn generate_columns(&mut self, _model: Model<Solving>, _pricer: SCIPPricer, _farkas: bool) -> PricerResult {
PricerResult {
state: PricerResultState::StopEarly,
lower_bound: None,
Expand All @@ -108,7 +158,7 @@ mod tests {
struct OptimalPricer;

impl Pricer for OptimalPricer {
fn generate_columns(&mut self, _model: Model<Solving>, _farkas: bool) -> PricerResult {
fn generate_columns(&mut self, _model: Model<Solving>, _pricer: SCIPPricer, _farkas: bool) -> PricerResult {
PricerResult {
state: PricerResultState::NoColumns,
lower_bound: None,
Expand Down Expand Up @@ -144,7 +194,7 @@ mod tests {
}

impl Pricer for AddSameColumnPricer {
fn generate_columns(&mut self, mut model: Model<Solving>, _farkas: bool) -> PricerResult {
fn generate_columns(&mut self, mut model: Model<Solving>, _pricer: SCIPPricer, _farkas: bool) -> PricerResult {
assert_eq!(self.data.a, (0..1000).collect::<Vec<usize>>());
if self.added {
PricerResult {
Expand Down Expand Up @@ -196,4 +246,34 @@ mod tests {
.solve();
assert_eq!(solved.status(), Status::Optimal);
}

struct InternalSCIPPricerTester;

impl Pricer for InternalSCIPPricerTester {
fn generate_columns(&mut self, _model: Model<Solving>, pricer: SCIPPricer, _farkas: bool) -> PricerResult {
assert_eq!(pricer.name(), "internal");
assert_eq!(pricer.desc(), "internal pricer");
assert_eq!(pricer.priority(), 100);
assert!(!pricer.is_delayed());
assert!(pricer.is_active());
PricerResult {
state: PricerResultState::NoColumns,
lower_bound: None,
}
}
}

#[test]
fn internal_pricer() {
let pricer = InternalSCIPPricerTester {};

let model = crate::model::Model::new()
.hide_output()
.include_default_plugins()
.read_prob("data/test/simple.lp")
.unwrap()
.include_pricer("internal", "internal pricer", 100, false, Box::new(pricer));

model.solve();
}
}
5 changes: 3 additions & 2 deletions src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ impl From<ffi::SCIP_ROWORIGINTYPE> for RowOrigin {

#[cfg(test)]
mod tests {
use crate::{
use crate::Event;
use crate::{
minimal_model, EventMask, Eventhdlr, HasScipPtr, Model, ModelWithProblem, ProblemOrSolving,
Solving, VarType,
};
Expand All @@ -237,7 +238,7 @@ mod tests {
EventMask::FIRST_LP_SOLVED
}

fn execute(&mut self, model: Model<Solving>) {
fn execute(&mut self, model: Model<Solving>, _eventhdlr: crate::SCIPEventhdlr, _event: Event) {
let first_cons = model.conss()[0].clone();
let row = first_cons.row().unwrap();
assert_eq!(row.n_non_zeroes(), 1);
Expand Down
Loading

0 comments on commit b87a799

Please sign in to comment.