use crate::builder::CanBeAddedToModel;
use crate::{
Constraint, Model, ModelStageProblemOrSolving, ModelStageWithProblem, ModelWithProblem,
ProblemOrSolving, Variable,
};
#[derive(Debug)]
pub struct ConsBuilder<'a> {
pub(crate) lhs: f64,
pub(crate) rhs: f64,
pub(crate) name: Option<&'a str>,
pub(crate) coefs: Vec<(&'a Variable, f64)>,
pub(crate) modifiable: Option<bool>,
pub(crate) removable: Option<bool>,
pub(crate) separated: Option<bool>,
}
pub fn cons() -> ConsBuilder<'static> {
ConsBuilder::default()
}
impl Default for ConsBuilder<'_> {
fn default() -> Self {
ConsBuilder {
lhs: f64::NEG_INFINITY,
rhs: f64::INFINITY,
name: None,
coefs: Vec::new(),
modifiable: None,
removable: None,
separated: None,
}
}
}
impl<'a> ConsBuilder<'a> {
pub fn le(mut self, val: f64) -> Self {
self.rhs = val;
self.lhs = f64::NEG_INFINITY;
self
}
pub fn ge(mut self, val: f64) -> Self {
self.lhs = val;
self.rhs = f64::INFINITY;
self
}
pub fn eq(mut self, val: f64) -> Self {
self.lhs = val;
self.rhs = val;
self
}
pub fn name(mut self, name: &'a str) -> Self {
self.name = Some(name);
self
}
pub fn coef(mut self, var: &'a Variable, coef: f64) -> Self {
self.coefs.push((var, coef));
self
}
pub fn coefs(mut self, var_refs: Vec<&'a Variable>, vals: Vec<f64>) -> Self {
self.coefs.extend(var_refs.into_iter().zip(vals));
self
}
pub fn expr<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = (&'a Variable, f64)>,
{
self.coefs.extend(iter);
self
}
pub fn modifiable(mut self, modifiable: bool) -> Self {
self.modifiable = Some(modifiable);
self
}
pub fn removable(mut self, removable: bool) -> Self {
self.removable = Some(removable);
self
}
pub fn separated(mut self, separate: bool) -> Self {
self.separated = Some(separate);
self
}
}
impl<S> CanBeAddedToModel<S> for ConsBuilder<'_>
where
S: ModelStageProblemOrSolving + ModelStageWithProblem,
{
type Return = Constraint;
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 {
vars.push(var);
coefs.push(coef);
}
let name = self.name.map(|s| s.to_string()).unwrap_or_else(|| {
let n_cons = model.n_conss();
format!("cons{}", n_cons)
});
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
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::var::var;
use crate::minimal_model;
#[test]
fn test_cons_builder() {
let mut model = minimal_model().hide_output();
let var = model.add(var().bin().obj(1.));
let cons = cons().name("c").eq(1.0).coef(&var, 1.0);
assert_eq!(cons.name, Some("c"));
assert_eq!(cons.lhs, 1.0);
assert_eq!(cons.rhs, 1.0);
assert_eq!(cons.coefs.len(), 1);
assert_eq!(cons.coefs[0].1, 1.0);
model.add(cons);
assert_eq!(model.n_conss(), 1);
let cons = &model.conss()[0];
assert_eq!(cons.name(), "c");
let solved = model.solve();
assert_eq!(solved.status(), crate::Status::Optimal);
assert_eq!(solved.obj_val(), 1.0);
}
#[test]
fn test_cons_builder_expr() {
let mut model = minimal_model().hide_output();
let vars = [
model.add(var().bin().obj(1.)),
model.add(var().bin().obj(1.)),
];
let cons = cons()
.name("c")
.eq(1.0)
.expr(vars.iter().map(|var| (var, 1.0)));
assert_eq!(cons.name, Some("c"));
assert_eq!(cons.lhs, 1.0);
assert_eq!(cons.rhs, 1.0);
assert_eq!(cons.coefs.len(), 2);
assert_eq!(cons.coefs[0].1, 1.0);
assert_eq!(cons.coefs[1].1, 1.0);
model.add(cons);
assert_eq!(model.n_conss(), 1);
let cons = &model.conss()[0];
assert_eq!(cons.name(), "c");
let solved = model.solve();
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));
}
}