use std::time::Duration;
use microlp::{Error, StopReason};
use crate::variable::{UnsolvedProblem, VariableDefinition};
use crate::{Constraint, Variable};
use crate::{
constraint::ConstraintReference,
solvers::{
ObjectiveDirection, ResolutionError, Solution, SolutionStatus, SolverModel, WithTimeLimit,
},
};
pub fn microlp(to_solve: UnsolvedProblem) -> MicroLpProblem {
let UnsolvedProblem {
objective,
direction,
variables,
} = to_solve;
let mut problem = microlp::Problem::new(match direction {
ObjectiveDirection::Maximisation => microlp::OptimizationDirection::Maximize,
ObjectiveDirection::Minimisation => microlp::OptimizationDirection::Minimize,
});
let variables: Vec<microlp::Variable> = variables
.iter_variables_with_def()
.map(
|(
var,
&VariableDefinition {
min,
max,
is_integer,
..
},
)| {
let coeff = *objective.linear.coefficients.get(&var).unwrap_or(&0.);
if is_integer {
problem.add_integer_var(coeff, (min as i32, max as i32))
} else {
problem.add_var(coeff, (min, max))
}
},
)
.collect();
MicroLpProblem {
problem,
variables,
n_constraints: 0,
}
}
pub struct MicroLpProblem {
problem: microlp::Problem,
variables: Vec<microlp::Variable>,
n_constraints: usize,
}
impl MicroLpProblem {
pub fn as_inner(&self) -> µlp::Problem {
&self.problem
}
}
impl WithTimeLimit for MicroLpProblem {
fn with_time_limit<T: Into<f64>>(mut self, seconds: T) -> Self {
self.problem
.set_time_limit(Duration::from_secs_f64(seconds.into()));
self
}
}
impl SolverModel for MicroLpProblem {
type Solution = MicroLpSolution;
type Error = ResolutionError;
fn solve(self) -> Result<Self::Solution, Self::Error> {
let solution = self.problem.solve()?;
Ok(MicroLpSolution {
solution,
variables: self.variables,
})
}
fn add_constraint(&mut self, constraint: Constraint) -> ConstraintReference {
let index = self.n_constraints;
let op = match constraint.is_equality {
true => microlp::ComparisonOp::Eq,
false => microlp::ComparisonOp::Le,
};
let constant = -constraint.expression.constant;
let mut linear_expr = microlp::LinearExpr::empty();
for (var, coefficient) in constraint.expression.linear.coefficients {
linear_expr.add(self.variables[var.index()], coefficient);
}
self.problem.add_constraint(linear_expr, op, constant);
self.n_constraints += 1;
ConstraintReference { index }
}
fn name() -> &'static str {
"Microlp"
}
}
impl From<microlp::Error> for ResolutionError {
fn from(microlp_error: Error) -> Self {
match microlp_error {
microlp::Error::Unbounded => Self::Unbounded,
microlp::Error::Infeasible => Self::Infeasible,
microlp::Error::InternalError(s) => Self::Str(s),
}
}
}
pub struct MicroLpSolution {
solution: microlp::Solution,
variables: Vec<microlp::Variable>,
}
impl MicroLpSolution {
pub fn into_inner(self) -> microlp::Solution {
self.solution
}
}
impl Solution for MicroLpSolution {
fn status(&self) -> SolutionStatus {
let solution_kind = self.solution.stop_reason();
match solution_kind {
StopReason::Finished => SolutionStatus::Optimal,
StopReason::Limit => SolutionStatus::TimeLimit,
}
}
fn value(&self, variable: Variable) -> f64 {
self.solution.var_value(self.variables[variable.index()])
}
}
#[cfg(test)]
mod tests {
use crate::{Solution, SolverModel, variable, variables};
use super::microlp;
#[test]
fn can_solve_easy() {
let mut vars = variables!();
let x = vars.add(variable().clamp(0, 2));
let y = vars.add(variable().clamp(1, 3));
let solution = vars
.maximise(x + y)
.using(microlp)
.with((2 * x + y) << 4)
.solve()
.unwrap();
assert_eq!((solution.value(x), solution.value(y)), (0.5, 3.))
}
#[test]
fn can_solve_milp() {
let mut vars = variables!();
let x = vars.add(variable().clamp(2, f64::INFINITY));
let y = vars.add(variable().clamp(0, 7));
let z = vars.add(variable().integer().clamp(0, f64::INFINITY));
let solution = vars
.maximise(50 * x + 40 * y + 45 * z)
.using(microlp)
.with((3 * x + 2 * y + z) << 20)
.with((2 * x + y + 3 * z) << 15)
.solve()
.unwrap();
assert_eq!(
(solution.value(x), solution.value(y), solution.value(z)),
(2.0, 6.5, 1.0)
)
}
}