// Copyright (c) 2016, 2017, 2018, 2019 Frank Fischer <frank-fischer@shadow-soft.de>
//
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>
//
#![allow(unused_unsafe)]
use crate::{DVector, Real};
use c_str_macro::c_str;
use cplex_sys as cpx;
use cplex_sys::trycpx;
use std;
use std::ffi::CString;
use std::ptr;
use std::result;
use std::os::raw::{c_char, c_double, c_int};
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Solver(cpx::CplexError),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
match self {
Solver(err) => Some(err),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use Error::*;
match self {
Solver(err) => err.fmt(fmt),
}
}
}
impl From<cpx::CplexError> for Error {
fn from(err: cpx::CplexError) -> Error {
Error::Solver(err)
}
}
pub type Result<T> = result::Result<T, Error>;
pub struct Solver {
env: *mut cpx::Env,
net: *mut cpx::Net,
}
impl Drop for Solver {
fn drop(&mut self) {
unsafe {
cpx::NETfreeprob(self.env, &mut self.net);
}
unsafe { cpx::closeCPLEX(&mut self.env) };
}
}
impl Solver {
pub fn new(nnodes: usize) -> Result<Solver> {
let mut status: c_int;
let env;
let mut net = std::ptr::null_mut();
unsafe {
trycpx!({
let mut status = 0;
env = cpx::openCPLEX(&mut status);
status
});
#[allow(clippy::never_loop)]
loop {
status = if cfg!(debug_assertions) {
cpx::setlogfilename(env, c_str!("mcf.cpxlog").as_ptr(), c_str!("w").as_ptr())
} else {
0
};
if status != 0 {
break;
}
net = cpx::NETcreateprob(env, &mut status, c_str!("mcf").as_ptr());
if status != 0 {
break;
}
status = cpx::NETaddnodes(env, net, nnodes as c_int, ptr::null(), ptr::null());
if status != 0 {
break;
}
status = cpx::NETchgobjsen(env, net, cpx::ObjectiveSense::Minimize.to_c());
if status != 0 {
break;
}
break;
}
if status != 0 {
let msg = CString::new(vec![0; cpx::MESSAGE_BUF_SIZE]).unwrap().into_raw();
cpx::geterrorstring(env, status, msg);
cpx::NETfreeprob(env, &mut net);
return Err(cpx::CplexError {
code: status,
msg: CString::from_raw(msg).to_string_lossy().into_owned(),
}
.into());
}
}
Ok(Solver { env, net })
}
pub fn num_nodes(&self) -> usize {
unsafe { cpx::NETgetnumnodes(self.env, self.net) as usize }
}
pub fn num_arcs(&self) -> usize {
unsafe { cpx::NETgetnumarcs(self.env, self.net) as usize }
}
pub fn set_balance(&mut self, node: usize, supply: Real) -> Result<()> {
let n = node as c_int;
let s = supply as c_double;
trycpx!(cpx::NETchgsupply(self.env, self.net, 1, &n, &s as *const c_double));
Ok(())
}
pub fn set_objective(&mut self, obj: &DVector) -> Result<()> {
let inds = (0..obj.len() as c_int).collect::<Vec<_>>();
trycpx!(cpx::NETchgobj(
self.env,
self.net,
obj.len() as c_int,
inds.as_ptr(),
obj.as_ptr()
));
Ok(())
}
pub fn add_arc(&mut self, src: usize, snk: usize, cost: Real, cap: Real) -> Result<()> {
let f = src as c_int;
let t = snk as c_int;
let c = cost as c_double;
let u = cap as c_double;
let name = CString::new(format!("x{}#{}_{}", self.num_arcs() + 1, f + 1, t + 1)).unwrap();
let cname = name.as_ptr();
trycpx!(cpx::NETaddarcs(
self.env,
self.net,
1,
&f,
&t,
ptr::null(),
&u,
&c,
&cname as *const *const c_char
));
Ok(())
}
pub fn solve(&mut self) -> Result<()> {
trycpx!(cpx::NETprimopt(self.env, self.net));
Ok(())
}
pub fn objective(&self) -> Result<Real> {
let mut objval: c_double = 0.0;
trycpx!(cpx::NETgetobjval(self.env, self.net, &mut objval as *mut c_double));
Ok(objval)
}
pub fn get_solution(&self) -> Result<DVector> {
let mut sol = dvec![0.0; self.num_arcs()];
let mut stat: c_int = 0;
let mut objval: c_double = 0.0;
trycpx!(cpx::NETsolution(
self.env,
self.net,
&mut stat as *mut c_int,
&mut objval as *mut c_double,
sol.as_mut_ptr(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut()
));
Ok(sol)
}
pub fn writelp(&self, filename: &str) -> Result<()> {
let fname = CString::new(filename).unwrap();
trycpx!(cpx::NETwriteprob(self.env, self.net, fname.as_ptr(), ptr::null_mut()));
Ok(())
}
}