#![forbid(missing_docs)]
use std::convert::{TryFrom, TryInto};
use std::ffi::{c_void, CString};
use std::ops::{Bound, RangeBounds};
use std::os::raw::c_int;
use highs_sys::*;
use crate::options::HighsOptionValue;
pub use matrix_col::{ColMatrix, Row};
pub use matrix_row::{Col, RowMatrix};
pub use status::{HighsModelStatus, HighsStatus};
pub type RowProblem = Problem<RowMatrix>;
pub type ColProblem = Problem<ColMatrix>;
mod matrix_col;
mod matrix_row;
mod options;
mod status;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Problem<MATRIX = ColMatrix> {
colcost: Vec<f64>,
collower: Vec<f64>,
colupper: Vec<f64>,
rowlower: Vec<f64>,
rowupper: Vec<f64>,
matrix: MATRIX,
}
impl<MATRIX: Default> Problem<MATRIX>
where
Problem<ColMatrix>: From<Problem<MATRIX>>,
{
pub fn num_cols(&self) -> usize {
self.colcost.len()
}
pub fn num_rows(&self) -> usize {
self.rowlower.len()
}
fn add_row_inner<N: Into<f64> + Copy, B: RangeBounds<N>>(&mut self, bounds: B) -> Row {
let r = Row(self.num_rows().try_into().unwrap());
let low = bound_value(bounds.start_bound()).unwrap_or(f64::NEG_INFINITY);
let high = bound_value(bounds.end_bound()).unwrap_or(f64::INFINITY);
self.rowlower.push(low);
self.rowupper.push(high);
r
}
fn add_column_inner<N: Into<f64> + Copy, B: RangeBounds<N>>(
&mut self,
col_factor: f64,
bounds: B,
) {
self.colcost.push(col_factor);
let low = bound_value(bounds.start_bound()).unwrap_or(f64::NEG_INFINITY);
let high = bound_value(bounds.end_bound()).unwrap_or(f64::INFINITY);
self.collower.push(low);
self.colupper.push(high);
}
pub fn optimise(self, sense: Sense) -> Model {
let mut m = Model::new(self);
m.set_sense(sense);
m
}
pub fn new() -> Self {
Self::default()
}
}
fn bound_value<N: Into<f64> + Copy>(b: Bound<&N>) -> Option<f64> {
match b {
Bound::Included(v) | Bound::Excluded(v) => Some((*v).into()),
Bound::Unbounded => None,
}
}
fn c(n: usize) -> c_int {
n.try_into().unwrap()
}
#[derive(Debug)]
pub struct Model {
highs: HighsPtr,
}
#[derive(Debug)]
pub struct SolvedModel {
highs: HighsPtr,
}
#[repr(C)]
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum Sense {
Maximise = -1,
Minimise = 1,
}
impl Model {
pub fn set_sense(&mut self, sense: Sense) {
let ret = unsafe { Highs_changeObjectiveSense(self.highs.mut_ptr(), sense as c_int) };
assert_eq!(ret, 1, "changeObjectiveSense failed");
}
pub fn new<P: Into<Problem<ColMatrix>>>(problem: P) -> Self {
let mut highs = HighsPtr::default();
let problem = problem.into();
log::debug!(
"Adding a problem with {} variables and {} constraints to HiGHS",
problem.num_cols(),
problem.num_rows()
);
unsafe {
handle_status(Highs_passLp(
highs.mut_ptr(),
c(problem.num_cols()),
c(problem.num_rows()),
c(problem.matrix.avalue.len()),
problem.colcost.as_ptr(),
problem.collower.as_ptr(),
problem.colupper.as_ptr(),
problem.rowlower.as_ptr(),
problem.rowupper.as_ptr(),
problem.matrix.astart.as_ptr(),
problem.matrix.aindex.as_ptr(),
problem.matrix.avalue.as_ptr(),
));
}
Self { highs }
}
pub fn make_quiet(&mut self) {
handle_status(unsafe { Highs_runQuiet(self.highs.mut_ptr()) })
}
pub fn set_option<STR: Into<Vec<u8>>, V: HighsOptionValue>(&mut self, option: STR, value: V) {
let c_str = CString::new(option).expect("invalid option name");
handle_status(unsafe { value.set_option(self.highs.mut_ptr(), c_str.as_ptr()) });
}
pub fn solve(mut self) -> SolvedModel {
unsafe {
handle_status(Highs_run(self.highs.mut_ptr()));
}
SolvedModel { highs: self.highs }
}
}
impl From<SolvedModel> for Model {
fn from(solved: SolvedModel) -> Self {
Self {
highs: solved.highs,
}
}
}
#[derive(Debug)]
struct HighsPtr(*mut c_void);
impl Drop for HighsPtr {
fn drop(&mut self) {
unsafe { Highs_destroy(self.0) }
}
}
impl Default for HighsPtr {
fn default() -> Self {
Self(unsafe { Highs_create() })
}
}
impl HighsPtr {
#[allow(dead_code)]
const fn ptr(&self) -> *const c_void {
self.0
}
unsafe fn unsafe_mut_ptr(&self) -> *mut c_void {
self.0
}
fn mut_ptr(&mut self) -> *mut c_void {
self.0
}
}
impl SolvedModel {
pub fn status(&self) -> HighsModelStatus {
let model_status = unsafe { Highs_getModelStatus(self.highs.unsafe_mut_ptr(), 0) };
HighsModelStatus::try_from(model_status).unwrap()
}
pub fn get_solution(&self) -> Solution {
let cols = self.num_cols();
let rows = self.num_rows();
let mut colvalue: Vec<f64> = vec![0.; cols];
let mut coldual: Vec<f64> = vec![0.; cols];
let mut rowvalue: Vec<f64> = vec![0.; rows];
let mut rowdual: Vec<f64> = vec![0.; rows];
unsafe {
Highs_getSolution(
self.highs.unsafe_mut_ptr(),
colvalue.as_mut_ptr(),
coldual.as_mut_ptr(),
rowvalue.as_mut_ptr(),
rowdual.as_mut_ptr(),
);
}
Solution {
colvalue,
coldual,
rowvalue,
rowdual,
}
}
fn num_cols(&self) -> usize {
let n = unsafe { Highs_getNumCols(self.highs.unsafe_mut_ptr()) };
n.try_into().unwrap()
}
fn num_rows(&self) -> usize {
let n = unsafe { Highs_getNumRows(self.highs.unsafe_mut_ptr()) };
n.try_into().unwrap()
}
}
#[derive(Clone, Debug)]
pub struct Solution {
colvalue: Vec<f64>,
coldual: Vec<f64>,
rowvalue: Vec<f64>,
rowdual: Vec<f64>,
}
impl Solution {
pub fn columns(&self) -> &[f64] {
&self.colvalue
}
pub fn dual_columns(&self) -> &[f64] {
&self.coldual
}
pub fn rows(&self) -> &[f64] {
&self.rowvalue
}
pub fn dual_rows(&self) -> &[f64] {
&self.rowdual
}
}
fn handle_status(status: c_int) {
match HighsStatus::try_from(status).unwrap() {
HighsStatus::OK => {}
HighsStatus::Warning => {
log::warn!("Warning from HiGHS !");
}
HighsStatus::Error => {
panic!("An error was encountered in HiGHS.");
}
}
}