use std::convert::TryInto;
use coin_cbc::{raw::Status, Col, Model, Sense, Solution as CbcSolution};
use crate::solvers::ModelWithSOS1;
use crate::variable::{UnsolvedProblem, VariableDefinition};
use crate::{
constraint::ConstraintReference,
solvers::{ObjectiveDirection, ResolutionError, Solution, SolverModel},
IntoAffineExpression,
};
use crate::{Constraint, Variable};
pub fn coin_cbc(to_solve: UnsolvedProblem) -> CoinCbcProblem {
let UnsolvedProblem {
objective,
direction,
variables,
} = to_solve;
let mut model = Model::default();
let columns: Vec<Col> = variables
.into_iter()
.map(
|VariableDefinition {
min,
max,
is_integer,
..
}| {
let col = model.add_col();
model.set_col_lower(col, min);
if max < f64::INFINITY {
model.set_col_upper(col, max)
}
if is_integer {
model.set_integer(col);
}
col
},
)
.collect();
for (var, coeff) in objective.linear.coefficients.into_iter() {
model.set_obj_coeff(columns[var.index()], coeff);
}
model.set_obj_sense(match direction {
ObjectiveDirection::Maximisation => Sense::Maximize,
ObjectiveDirection::Minimisation => Sense::Minimize,
});
CoinCbcProblem {
model,
columns,
has_sos: false,
}
}
pub struct CoinCbcProblem {
model: Model,
columns: Vec<Col>,
has_sos: bool,
}
impl CoinCbcProblem {
pub fn as_inner(&self) -> &Model {
&self.model
}
pub fn as_inner_mut(&mut self) -> &mut Model {
&mut self.model
}
pub fn set_parameter(&mut self, key: &str, value: &str) {
self.model.set_parameter(key, value);
}
}
impl SolverModel for CoinCbcProblem {
type Solution = CoinCbcSolution;
type Error = ResolutionError;
fn solve(mut self) -> Result<Self::Solution, Self::Error> {
if self.has_sos {
let dummy_col1 = self.model.add_col();
let dummy_col2 = self.model.add_col();
self.model.set_obj_coeff(dummy_col1, 1e-6);
self.model.set_obj_coeff(dummy_col2, 1e-6);
self.model.set_integer(dummy_col1);
let dummy_row = self.model.add_row();
self.model.set_weight(dummy_row, dummy_col1, 1.);
self.model.set_weight(dummy_row, dummy_col2, 1.);
self.model.set_row_upper(dummy_row, 1.);
}
let solution = self.model.solve();
let raw = solution.raw();
match raw.status() {
Status::Stopped => Err(ResolutionError::Other("Stopped")),
Status::Abandoned => Err(ResolutionError::Other("Abandoned")),
Status::UserEvent => Err(ResolutionError::Other("UserEvent")),
Status::Finished | Status::Unlaunched => {
if raw.is_continuous_unbounded() {
Err(ResolutionError::Unbounded)
} else if raw.is_proven_infeasible() {
Err(ResolutionError::Infeasible)
} else {
let solution_vec = solution.raw().col_solution().into();
Ok(CoinCbcSolution {
solution,
solution_vec,
})
}
},
}
}
fn add_constraint(&mut self, constraint: Constraint) -> ConstraintReference {
let index = self.model.num_rows().try_into().unwrap();
let row = self.model.add_row();
let constant = -constraint.expression.constant;
if constraint.is_equality {
self.model.set_row_equal(row, constant);
} else {
self.model.set_row_upper(row, constant);
}
for (var, coeff) in constraint.expression.linear.coefficients.into_iter() {
self.model.set_weight(row, self.columns[var.index()], coeff);
}
ConstraintReference { index }
}
}
impl ModelWithSOS1 for CoinCbcProblem {
fn add_sos1<I: IntoAffineExpression>(&mut self, variables: I) {
let columns = std::mem::take(&mut self.columns);
let cols_and_weights = variables
.linear_coefficients()
.into_iter()
.map(|(var, weight)| (columns[var.index()], weight));
self.model.add_sos1(cols_and_weights);
self.columns = columns;
self.has_sos = true;
}
}
pub struct CoinCbcSolution {
solution: CbcSolution,
solution_vec: Vec<f64>, }
impl CoinCbcSolution {
pub fn model(&self) -> &coin_cbc::raw::Model {
self.solution.raw()
}
}
impl Solution for CoinCbcSolution {
fn value(&self, variable: Variable) -> f64 {
self.solution_vec[variable.index()]
}
}