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
7 changes: 4 additions & 3 deletions examples/most_infeasible_branching.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use russcip::prelude::*;
use russcip::ParamSetting;
use russcip::Solving;
use russcip::{
prelude::*, BranchRule, BranchingCandidate, BranchingResult, ParamSetting, SCIPBranchRule,
Solving,
};

/// A branching rule that implements the most infeasible branching strategy.
/// It selects the variable with the highest fractionality (closest to 0.5) to branch on.
Expand Down
180 changes: 161 additions & 19 deletions src/builder/cons.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::builder::CanBeAddedToModel;
use crate::{
Constraint, Model, ModelWithProblem, ProblemCreated, ProblemOrSolving, Solving, Variable,
Constraint, Model, ModelStageProblemOrSolving, ModelStageWithProblem, ModelWithProblem,
ProblemOrSolving, Variable,
};

/// A builder for creating constraints.
Expand All @@ -14,6 +15,12 @@ pub struct ConsBuilder<'a> {
pub(crate) name: Option<&'a str>,
/// Coefficients of constraint
pub(crate) coefs: Vec<(&'a Variable, f64)>,
/// Modifiable flag of constraint
pub(crate) modifiable: Option<bool>,
/// Removable flag of constraint
pub(crate) removable: Option<bool>,
/// Separated flag of constraint
pub(crate) separated: Option<bool>,
}

/// Creates a new default `ConsBuilder`.
Expand All @@ -28,6 +35,9 @@ impl Default for ConsBuilder<'_> {
rhs: f64::INFINITY,
name: None,
coefs: Vec::new(),
modifiable: None,
removable: None,
separated: None,
}
}
}
Expand Down Expand Up @@ -79,29 +89,32 @@ impl<'a> ConsBuilder<'a> {
self.coefs.extend(iter);
self
}
}

impl CanBeAddedToModel<ProblemCreated> for ConsBuilder<'_> {
type Return = Constraint;
fn add(self, model: &mut Model<ProblemCreated>) -> Self::Return {
let mut vars = Vec::new();
let mut coefs = Vec::new();
for (var, coef) in self.coefs {
vars.push(var);
coefs.push(coef);
}
/// Sets the modifiable flag of the constraint
pub fn modifiable(mut self, modifiable: bool) -> Self {
self.modifiable = Some(modifiable);
self
}

let name = self.name.map(|s| s.to_string()).unwrap_or_else(|| {
let n_cons = model.n_conss();
format!("cons{}", n_cons)
});
model.add_cons(vars, &coefs, self.lhs, self.rhs, &name)
/// Sets the removable flag of the constraint
pub fn removable(mut self, removable: bool) -> Self {
self.removable = Some(removable);
self
}

/// Sets whether the constraint should be separated during LP processing
pub fn separated(mut self, separate: bool) -> Self {
self.separated = Some(separate);
self
}
}

impl CanBeAddedToModel<Solving> for ConsBuilder<'_> {
impl<S> CanBeAddedToModel<S> for ConsBuilder<'_>
where
S: ModelStageProblemOrSolving + ModelStageWithProblem,
{
type Return = Constraint;
fn add(self, model: &mut Model<Solving>) -> Self::Return {
fn add(self, model: &mut Model<S>) -> Self::Return {
let mut vars = Vec::new();
let mut coefs = Vec::new();
for (var, coef) in self.coefs {
Expand All @@ -113,7 +126,19 @@ impl CanBeAddedToModel<Solving> for ConsBuilder<'_> {
let n_cons = model.n_conss();
format!("cons{}", n_cons)
});
model.add_cons(vars, &coefs, self.lhs, self.rhs, &name)
let cons = model.add_cons(vars, &coefs, self.lhs, self.rhs, &name);

if let Some(modifiable) = self.modifiable {
model.set_cons_modifiable(&cons, modifiable);
}
if let Some(removable) = self.removable {
model.set_cons_removable(&cons, removable);
}
if let Some(separate) = self.separated {
model.set_cons_separated(&cons, separate);
}

cons
}
}

Expand Down Expand Up @@ -179,4 +204,121 @@ mod tests {
assert_eq!(solved.status(), crate::Status::Optimal);
assert_eq!(solved.obj_val(), 1.0);
}

#[test]
fn test_cons_builder_modifiable() {
let mut model = minimal_model().hide_output();
let vars = [
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
];

let cb1 = cons()
.name("c1")
.le(2.0)
.expr(vars.iter().map(|var| (var, 1.0)))
.modifiable(true);

let cb2 = cons()
.name("c2")
.ge(1.0)
.expr(vars.iter().map(|var| (var, 1.0)))
.modifiable(false);

let cb3 = cons().name("c3").ge(1.0).coef(&vars[0], 1.0);

assert_eq!(cb1.modifiable, Some(true));
assert_eq!(cb2.modifiable, Some(false));
assert_eq!(cb3.modifiable, None);

let cons1 = model.add(cb1);
let cons2 = model.add(cb2);
let cons3 = model.add(cb3);

assert!(cons1.is_modifiable());
assert!(!cons2.is_modifiable());
assert!(!cons3.is_modifiable());

let solved = model.solve();
assert!(solved.cons_is_modifiable(&cons1));
assert!(!solved.cons_is_modifiable(&cons2));
assert!(!solved.cons_is_modifiable(&cons3));
}

#[test]
fn test_cons_builder_removable() {
let mut model = minimal_model().hide_output();
let vars = [
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
];

let cb1 = cons()
.name("c1")
.le(2.0)
.expr(vars.iter().map(|var| (var, 1.0)))
.removable(true);

let cb2 = cons()
.name("c2")
.ge(1.0)
.expr(vars.iter().map(|var| (var, 1.0)))
.removable(false);

let cb3 = cons().name("c3").ge(1.0).coef(&vars[0], 1.0);

assert_eq!(cb1.removable, Some(true));
assert_eq!(cb2.removable, Some(false));
assert_eq!(cb3.removable, None);

let cons1 = model.add(cb1);
let cons2 = model.add(cb2);

assert!(cons1.is_removable());
assert!(!cons2.is_removable());

let solved = model.solve();
assert!(solved.cons_is_removable(&cons1));
assert!(!solved.cons_is_removable(&cons2));
}

#[test]
fn test_cons_builder_separated() {
let mut model = minimal_model().hide_output();
let vars = [
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
];

let cb1 = cons()
.name("c1")
.le(2.0)
.expr(vars.iter().map(|var| (var, 1.0)))
.separated(true);

let cb2 = cons()
.name("c2")
.ge(1.0)
.expr(vars.iter().map(|var| (var, 1.0)))
.separated(false);

let cb3 = cons().name("c3").ge(1.0).coef(&vars[0], 1.0);

assert_eq!(cb1.separated, Some(true));
assert_eq!(cb2.separated, Some(false));
assert_eq!(cb3.separated, None);

let cons1 = model.add(cb1);
let cons2 = model.add(cb2);

assert!(cons1.is_separated());
assert!(!cons2.is_separated());

let solved = model.solve();
assert!(solved.cons_is_separated(&cons1));
assert!(!solved.cons_is_separated(&cons2));
}
}
15 changes: 15 additions & 0 deletions src/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ impl Constraint {
Some(unsafe { ffi::SCIPgetDualsolLinear(self.scip.raw, self.raw) })
}

/// Returns the modifiable flag of the constraint
pub fn is_modifiable(&self) -> bool {
self.scip.cons_is_modifiable(self)
}

/// Returns the removable flag of the constraint
pub fn is_removable(&self) -> bool {
self.scip.cons_is_removable(self)
}

/// Returns whether the constraint should be separated during LP processing
pub fn is_separated(&self) -> bool {
self.scip.cons_is_separated(self)
}

/// Returns the corresponding transformed constraint.
/// Returns `None` if the transformed constraint does not exist (yet).
pub fn transformed(&self) -> Option<Constraint> {
Expand Down
62 changes: 55 additions & 7 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,6 @@ impl Model<ProblemCreated> {
self.set_obj_sense(ObjSense::Minimize)
}

/// Sets the constraint as modifiable or not.
pub fn set_cons_modifiable(&mut self, cons: &Constraint, modifiable: bool) {
self.scip
.set_cons_modifiable(cons, modifiable)
.expect("Failed to set constraint modifiable");
}

/// Informs the SCIP instance that the objective value is always integral and returns the same `Model` instance.
#[allow(unused_mut)]
pub fn set_obj_integral(mut self) -> Self {
Expand Down Expand Up @@ -674,6 +667,15 @@ pub trait ModelWithProblem {
/// Returns a vector of all constraints in the optimization model.
fn conss(&self) -> Vec<Constraint>;

/// Returns the modifiable flag of the given constraint
fn cons_is_modifiable(&self, cons: &Constraint) -> bool;

/// Returns the removable flag of the given constraint
fn cons_is_removable(&self, cons: &Constraint) -> bool;

/// Returns whether the constraint should be separated during LP processing
fn cons_is_separated(&self, cons: &Constraint) -> bool;

/// Writes the optimization model to a file with the given path and extension.
fn write(&self, path: &str, ext: &str) -> Result<(), Retcode>;
}
Expand Down Expand Up @@ -734,6 +736,7 @@ impl<S: ModelStageWithProblem> ModelWithProblem for Model<S> {
self.scip.n_conss()
}

/// Finds a constraint using its name
fn find_cons(&self, name: &str) -> Option<Constraint> {
self.scip.find_cons(name).map(|cons| Constraint {
raw: cons,
Expand All @@ -753,6 +756,21 @@ impl<S: ModelStageWithProblem> ModelWithProblem for Model<S> {
.collect()
}

/// Returns the modifiable flag of the given constraint
fn cons_is_modifiable(&self, cons: &Constraint) -> bool {
self.scip.cons_is_modifiable(cons)
}

/// Returns the removable flag of the given constraint
fn cons_is_removable(&self, cons: &Constraint) -> bool {
self.scip.cons_is_removable(cons)
}

/// Returns whether the constraint should be separated during LP processing
fn cons_is_separated(&self, cons: &Constraint) -> bool {
self.scip.cons_is_separated(cons)
}

/// Writes the optimization model to a file with the given path and extension.
fn write(&self, path: &str, ext: &str) -> Result<(), Retcode> {
self.scip.write(path, ext)?;
Expand Down Expand Up @@ -949,6 +967,15 @@ pub trait ProblemOrSolving {
rhs: f64,
name: &str,
) -> Constraint;

/// Sets the constraint as modifiable or not.
fn set_cons_modifiable(&mut self, cons: &Constraint, modifiable: bool);

/// Sets the constraint as removable or not.
fn set_cons_removable(&mut self, cons: &Constraint, removable: bool);

/// Sets whether the constraint should be separated during LP processing
fn set_cons_separated(&mut self, cons: &Constraint, separate: bool);
}

/// A trait for model stages that have a problem or are during solving.
Expand Down Expand Up @@ -1257,6 +1284,27 @@ impl<S: ModelStageProblemOrSolving> ProblemOrSolving for Model<S> {
scip: self.scip.clone(),
}
}

/// Sets the constraint as modifiable or not.
fn set_cons_modifiable(&mut self, cons: &Constraint, modifiable: bool) {
self.scip
.set_cons_modifiable(cons, modifiable)
.expect("Failed to set constraint modifiable");
}

/// Sets the constraint as removable or not.
fn set_cons_removable(&mut self, cons: &Constraint, removable: bool) {
self.scip
.set_cons_removable(cons, removable)
.expect("Failed to set constraint removable");
}

/// Sets whether the constraint should be separated during LP processing
fn set_cons_separated(&mut self, cons: &Constraint, separate: bool) {
self.scip
.set_cons_separated(cons, separate)
.expect("Failed to set constraint separated");
}
}

/// A trait for optimization models with any state that might have solutions.
Expand Down
2 changes: 1 addition & 1 deletion src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ impl From<ffi::SCIP_ROWORIGINTYPE> for RowOrigin {
#[cfg(test)]
mod tests {
use crate::prelude::{cons, eventhdlr, var};
use crate::Event;
use crate::{minimal_model, EventMask, Eventhdlr, Model, ModelWithProblem, Solving};
use crate::{Event, ProblemOrSolving};

#[test]
fn test_row() {
Expand Down
Loading
Loading