Skip to content
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
63 changes: 21 additions & 42 deletions src/branchrule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl SCIPBranchRule {
mod tests {
use super::*;
use crate::model::ModelWithProblem;
use crate::prelude::branchrule;
use crate::{model::Model, status::Status, Solving};

struct FirstChoosingBranchingRule {
Expand All @@ -129,10 +130,6 @@ mod tests {
) -> BranchingResult {
self.chosen = Some(candidates[0].clone());
assert_eq!(branchrule.name(), "FirstChoosingBranchingRule");
assert_eq!(branchrule.desc(), "");
assert_eq!(branchrule.priority(), 100000);
assert_eq!(branchrule.maxdepth(), 1000);
assert_eq!(branchrule.maxbounddist(), 1.0);
BranchingResult::DidNotRun
}
}
Expand All @@ -141,21 +138,15 @@ mod tests {
fn choosing_first_branching_rule() {
let br = FirstChoosingBranchingRule { chosen: None };

let model = Model::new()
let mut model = Model::new()
.set_longint_param("limits/nodes", 2) // only call brancher once
.unwrap()
.hide_output()
.include_default_plugins()
.read_prob("data/test/gen-ip054.mps")
.unwrap()
.include_branch_rule(
"FirstChoosingBranchingRule",
"",
100000,
1000,
1.,
Box::new(br),
);
.unwrap();

model.add(branchrule(br).name("FirstChoosingBranchingRule"));

let solved = model.solve();
assert_eq!(solved.status(), Status::NodeLimit);
Expand All @@ -179,13 +170,13 @@ mod tests {
let br = CuttingOffBranchingRule {};

// create model from miplib instance gen-ip054
let model = Model::new()
let mut model = Model::new()
.hide_output()
.include_default_plugins()
.read_prob("data/test/gen-ip054.mps")
.unwrap()
.include_branch_rule("", "", 100000, 1000, 1., Box::new(br))
.solve();
.unwrap();
model.add(branchrule(br).maxdepth(10));
let model = model.solve();
assert_eq!(model.n_nodes(), 1);
}

Expand All @@ -205,7 +196,7 @@ mod tests {

#[test]
fn first_branching_rule() {
let model = Model::new()
let mut model = Model::new()
.hide_output()
.set_longint_param("limits/nodes", 2)
.unwrap() // only call brancher once
Expand All @@ -214,9 +205,8 @@ mod tests {
.unwrap();

let br = FirstBranchingRule;
let solved = model
.include_branch_rule("", "", 100000, 1000, 1., Box::new(br))
.solve();
model.add(branchrule(br).name("FirstBranchingRule").maxdepth(1000));
let solved = model.solve();

assert!(solved.n_nodes() > 1);
}
Expand All @@ -237,7 +227,7 @@ mod tests {

#[test]
fn custom_branching_rule() {
let model = Model::new()
let mut model = Model::new()
.hide_output()
.set_longint_param("limits/nodes", 2)
.unwrap() // only call brancher once
Expand All @@ -246,9 +236,8 @@ mod tests {
.unwrap();

let br = CustomBranchingRule;
let solved = model
.include_branch_rule("", "", 100000, 1000, 1., Box::new(br))
.solve();
model.add(branchrule(br));
let solved = model.solve();

assert!(solved.n_nodes() > 1);
}
Expand Down Expand Up @@ -283,7 +272,7 @@ mod tests {

#[test]
fn highest_bound_branch_rule() {
let model = Model::new()
let mut model = Model::new()
.hide_output()
.set_longint_param("limits/nodes", 2)
.unwrap() // only call brancher once
Expand All @@ -292,9 +281,8 @@ mod tests {
.unwrap();

let br = HighestBoundBranchRule;
let solved = model
.include_branch_rule("", "", 100000, 1000, 1., Box::new(br))
.solve();
model.add(branchrule(br));
let solved = model.solve();

assert!(solved.n_nodes() > 1);
}
Expand All @@ -314,7 +302,7 @@ mod tests {

#[test]
fn test_internal_scip_branch_rule() {
let model = Model::new()
let mut model = Model::new()
.hide_output()
.set_longint_param("limits/nodes", 2)
.unwrap() // only call brancher once
Expand All @@ -323,16 +311,7 @@ mod tests {
.unwrap();

let br = InternalBranchRuleDataTester;

model
.include_branch_rule(
"InternalBranchRuleDataTester",
"Internal branch rule data tester",
1000000,
1,
1.0,
Box::new(br),
)
.solve();
model.add(branchrule(br).maxdepth(1));
model.solve();
}
}
123 changes: 123 additions & 0 deletions src/builder/branchrule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::builder::CanBeAddedToModel;
use crate::{BranchRule, Model, ProblemCreated};

/// A builder for easily creating branch rules. It can be created using the `branch_rule` function.
pub struct BranchRuleBuilder<R: BranchRule> {
name: Option<String>,
desc: Option<String>,
priority: i32,
maxdepth: i32,
maxbounddist: f64,
rule: R,
}

impl<R: BranchRule> BranchRuleBuilder<R> {
/// Creates a new `BranchRuleBuilder` with the given branch rule.
///
/// Defaults:
/// - `name`: empty string
/// - `desc`: empty string
/// - `priority`: 100000
/// - `maxdepth`: -1 (unlimited)
/// - `maxbounddist`: 1.0 (applies on all nodes)
pub fn new(rule: R) -> Self {
BranchRuleBuilder {
name: None,
desc: None,
priority: 100000,
maxdepth: -1,
maxbounddist: 1.0,
rule,
}
}

/// Sets the name of the branch rule.
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}

/// Sets the description of the branch rule.
pub fn desc(mut self, desc: &str) -> Self {
self.desc = Some(desc.to_string());
self
}

/// Sets the priority of the branch rule.
///
/// When SCIP decides which branch rule to call, it considers their priorities.
/// A higher value indicates a higher priority.
pub fn priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}

/// Sets the maximum depth level up to which this branch rule should be used.
///
/// If this is -1, the branch rule can be used at any depth.
pub fn maxdepth(mut self, maxdepth: i32) -> Self {
self.maxdepth = maxdepth;
self
}

/// Sets the maximum relative bound distance from the current node's dual bound to
/// primal bound compared to the best node's dual bound for applying the branch rule.
///
/// A value of 0.0 means the rule can only be applied on the current best node,
/// while 1.0 means it can be applied on all nodes.
pub fn maxbounddist(mut self, maxbounddist: f64) -> Self {
self.maxbounddist = maxbounddist;
self
}
}

/// Creates a new default `BranchRuleBuilder` from a branch rule.
/// This function allows you to write:
/// ```rust
/// use russcip::{BranchRule, BranchingCandidate, BranchingResult, SCIPBranchRule, Solving};
/// use russcip::prelude::*;
///
/// struct MyBranchRule;
/// impl BranchRule for MyBranchRule {fn execute(&mut self, model: Model<Solving>, branchrule: SCIPBranchRule, candidates: Vec<BranchingCandidate>) -> BranchingResult {
/// todo!()
/// }
/// }
///
/// let rule = branchrule(MyBranchRule)
/// .name("My Branch Rule")
/// .desc("A custom branch rule")
/// .priority(100)
/// .maxdepth(10)
/// .maxbounddist(0.5);
///
/// let mut model = Model::default();
/// model.add(rule);
/// ```
pub fn branchrule<R: BranchRule>(rule: R) -> BranchRuleBuilder<R> {
BranchRuleBuilder::new(rule)
}

impl<R: BranchRule + 'static> CanBeAddedToModel for BranchRuleBuilder<R> {
type Return = ();

fn add(self, model: &mut Model<ProblemCreated>) {
// Use empty strings as defaults if name or description are not provided.
let name = self.name.unwrap_or_else(|| "".into());
let desc = self.desc.unwrap_or_else(|| "".into());
let rule_box = Box::new(self.rule);
model.include_branch_rule(
&name,
&desc,
self.priority,
self.maxdepth,
self.maxbounddist,
rule_box,
);
}
}

impl<R: BranchRule> From<R> for BranchRuleBuilder<R> {
fn from(rule: R) -> Self {
BranchRuleBuilder::new(rule)
}
}
72 changes: 72 additions & 0 deletions src/builder/eventhdlr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::builder::CanBeAddedToModel;
use crate::{Eventhdlr, Model, ProblemCreated};

/// A builder for easily creating event handlers. It can be created using the `eventhdlr` function.
pub struct EventHdlrBuilder<E: Eventhdlr> {
name: Option<String>,
desc: Option<String>,
eventhdlr: E,
}

impl<E: Eventhdlr> EventHdlrBuilder<E> {
/// Creates a new `EventHdlrBuilder` with the given event handler.
///
/// # Defaults
/// - `name`: empty string
/// - `desc`: empty string
pub fn new(eventhdlr: E) -> Self {
EventHdlrBuilder {
name: None,
desc: None,
eventhdlr,
}
}

/// Sets the name of the event handler.
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}

/// Sets the description of the event handler.
pub fn desc(mut self, desc: &str) -> Self {
self.desc = Some(desc.to_string());
self
}
}

/// Creates a new default `EventHdlrBuilder` from an event handler.
/// This function allows you to write:
/// ```rust
/// use russcip::prelude::*;
/// use russcip::{Event, EventMask, Eventhdlr, ProblemCreated, SCIPEventhdlr, Solving};
///
/// struct MyEventHandler;
/// impl Eventhdlr for MyEventHandler {
/// fn get_type(&self) -> EventMask {
/// todo!()
/// }
///
/// fn execute(&mut self, model: Model<Solving>, eventhdlr: SCIPEventhdlr, event: Event) {
/// todo!()
/// }
///
/// }
/// let ev = eventhdlr(MyEventHandler {}).name("My Event Handler");
/// let mut model = Model::default();
/// model.add(eventhdlr(MyEventHandler {}));
/// ```
pub fn eventhdlr<E: Eventhdlr>(ev: E) -> EventHdlrBuilder<E> {
EventHdlrBuilder::new(ev)
}

impl<E: Eventhdlr + 'static> CanBeAddedToModel for EventHdlrBuilder<E> {
type Return = ();

fn add(self, model: &mut Model<ProblemCreated>) {
let name = self.name.unwrap_or_else(|| "".into());
let desc = self.desc.unwrap_or_else(|| "".into());
let eventhdlr = Box::new(self.eventhdlr);
model.include_eventhdlr(&name, &desc, eventhdlr);
}
}
Loading
Loading