extern crate libc;
extern crate lpsolve_sys as lp;
#[macro_use]
extern crate bitflags;
pub mod error;
pub mod builder;
pub mod prelude;
pub use error::{LpSolveError, Result};
pub use builder::{ProblemBuilder, Solution, SolverConfig};
use std::ffi::{CStr, CString};
use std::io::Write;
use std::ops::Deref;
use error::{CReturnCode, OpContext, EntityType};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Verbosity {
Critical = 1,
Severe = 2,
Important = 3,
Normal = 4,
Detailed = 5,
Full = 6,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ConstraintType {
Le = 1,
Eq = 3,
Ge = 2,
Free = 0,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum SOSType {
Type1 = 1,
Type2 = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum VarType {
Binary = 1,
Float = 0,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum BoundsMode {
Restrictive = 1,
None = 0,
}
#[repr(C)]
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy)]
pub enum SimplexType {
PrimalPrimal = 5,
DualPrimal = 6,
PrimalDual = 9,
DualDual = 10,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum SolveStatus {
OutOfMemory = -2,
NotRun = -1,
Optimal = 0,
Suboptimal = 1,
Infeasible = 2,
Unbounded = 3,
Degenerate = 4,
NumericalFailure = 5,
UserAbort = 6,
Timeout = 7,
Presolved = 8,
ProcFail = 9,
ProcBreak = 11,
FeasibleFound = 12,
NoFeasibleFound = 13,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MPSOptions: ::libc::c_int {
const CRITICAL = 1;
const SEVERE = 2;
const IMPORTANT = 3;
const NORMAL = 4;
const DETAILED = 5;
const FULL = 6;
const FREE = 8;
const IBM = 16;
const NEGOBJCONST = 32;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PresolveMode: ::libc::c_int {
const NONE = 0;
const ROWS = 1;
const COLS = 2;
const LINDEP = 4;
const AGGREGATE = 8;
const SPARSER = 16;
const SOS = 32;
const REDUCEMIP = 64;
const KNAPSACK = 128;
const ELIMEQ2 = 256;
const IMPLIEDFREE = 512;
const REDUCEGCD = 1024;
const PROBEFIX = 2048;
const PROBEREDUCE = 4096;
const ROWDOMINATE = 8192;
const COLDOMINATE = 16384;
const MERGEROWS = 32768;
const IMPLIEDSLK = 65536;
const COLFIXDUAL = 131072;
const BOUNDS = 262144;
const DUALS = 524288;
const SENSDUALS = 1048576;
const TYPICAL = Self::ROWS.bits() | Self::COLS.bits();
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScalingMode: ::libc::c_int {
const NONE = 0;
const EXTREME = 1;
const RANGE = 2;
const MEAN = 3;
const GEOMETRIC = 4;
const FUTURE1 = 5;
const FUTURE2 = 6;
const CURTISREID = 7;
const QUADRATIC = 8;
const LOGARITHMIC = 16;
const POWER2 = 32;
const EQUILIBRATE = 64;
const INTEGERS = 128;
const DYNUPDATE = 256;
const ROWSONLY = 512;
const COLSONLY = 1024;
const TYPICAL = Self::GEOMETRIC.bits() | Self::EQUILIBRATE.bits() | Self::INTEGERS.bits();
const ALL = Self::EXTREME.bits() | Self::RANGE.bits() | Self::MEAN.bits() | Self::GEOMETRIC.bits();
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PivotRule {
FirstIndex = 0,
SteepestEdge = 1,
Devex = 2,
Random = 3,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BranchRule {
Ceiling = 0,
Floor = 1,
Automatic = 2,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AntiDegen: ::libc::c_int {
const NONE = 0;
const FIXEDVARS = 1;
const COLUMNCHECK = 2;
const STALLING = 4;
const NUMFAILURE = 8;
const LOSTFEAS = 16;
const INFEASIBLE = 32;
const DYNAMIC = 64;
const DURINGBB = 128;
const RHSPERTURB = 256;
const BOUNDFLIP = 512;
const DEFAULT = Self::FIXEDVARS.bits() | Self::STALLING.bits();
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ImproveMode: ::libc::c_int {
const NONE = 0;
const SOLUTION = 1;
const DUALFEAS = 2;
const THETAGAP = 4;
const BBSIMPLEX = 8;
const DEFAULT = Self::DUALFEAS.bits() | Self::THETAGAP.bits();
const INVERSE = Self::SOLUTION.bits() | Self::THETAGAP.bits();
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BasisCrash {
None = 0,
MostFeasible = 1,
LeastDegen = 2,
}
pub struct Problem {
lprec: *mut lp::lprec,
}
macro_rules! cptr {
($e:expr) => {
if $e.is_null() {
None
} else {
Some(Problem { lprec: $e })
}
};
}
unsafe extern "C" fn write_modeldata(
val: *mut libc::c_void,
buf: *mut libc::c_char,
) -> libc::c_int {
use std::panic::AssertUnwindSafe;
use std::panic::catch_unwind;
let result = catch_unwind(AssertUnwindSafe(|| unsafe {
let writer_ptr = val as *mut *mut dyn Write;
let writer = &mut **writer_ptr;
let buf = CStr::from_ptr(buf);
writer.write_all(buf.to_bytes())
}));
match result {
Ok(Ok(())) => 0, Ok(Err(_)) => 1, Err(_) => 1, }
}
impl Problem {
pub fn builder() -> ProblemBuilder {
ProblemBuilder::new()
}
fn check_buffer_and_bounds(
&self,
buffer_len: usize,
required: usize,
index: libc::c_int,
min: libc::c_int,
max: libc::c_int,
context: OpContext,
) -> Result<()> {
if buffer_len < required {
return Err(LpSolveError::BufferTooSmall {
required,
provided: buffer_len,
context,
});
}
if index < min || index > max {
return Err(LpSolveError::IndexOutOfBounds {
index,
min,
max,
context,
});
}
Ok(())
}
pub fn new(rows: libc::c_int, cols: libc::c_int) -> Option<Problem> {
let ptr = unsafe { lp::make_lp(rows, cols) };
cptr!(ptr)
}
pub fn read_lp<P: Deref<Target = CStr>, C: Deref<Target = CStr>>(
path: &P,
verbosity: Verbosity,
initial_name: &C,
) -> Option<Problem> {
let ptr = unsafe {
lp::read_LP(
path.as_ptr() as *mut _,
verbosity as libc::c_int,
initial_name.as_ptr() as *mut _,
)
};
cptr!(ptr)
}
pub fn read_freemps<P: Deref<Target = CStr>>(path: &P, options: MPSOptions) -> Option<Problem> {
let ptr = unsafe { lp::read_freeMPS(path.as_ptr() as *mut _, options.bits()) };
cptr!(ptr)
}
pub fn read_fixedmps<P: Deref<Target = CStr>>(
path: &P,
options: MPSOptions,
) -> Option<Problem> {
let ptr = unsafe { lp::read_MPS(path.as_ptr() as *mut _, options.bits()) };
cptr!(ptr)
}
pub fn write_lp(&self, out: &mut dyn Write) -> Result<()> {
let out_ptr = out as *mut dyn Write;
let out_ptr_ptr = &out_ptr as *const *mut dyn Write;
unsafe {
lp::write_lpex(
self.lprec,
out_ptr_ptr as *mut libc::c_void,
write_modeldata,
)
}.check_with(LpSolveError::WriteError)
}
pub fn write_fixedmps(&self, out: &mut dyn Write) -> Result<()> {
self.write_mps(out, 1)
}
pub fn write_freemps(&self, out: &mut dyn Write) -> Result<()> {
self.write_mps(out, 2)
}
pub fn write_mps(&self, out: &mut dyn Write, formatting: libc::c_int) -> Result<()> {
debug_assert!(formatting == 1 || formatting == 2);
let out_ptr = out as *mut dyn Write;
let out_ptr_ptr = &out_ptr as *const *mut dyn Write;
unsafe {
lp::MPS_writefileex(
self.lprec,
formatting,
out_ptr_ptr as *mut libc::c_void,
write_modeldata,
)
}.check_with(LpSolveError::WriteError)
}
pub fn resize(&mut self, rows: libc::c_int, cols: libc::c_int) -> Result<()> {
unsafe { lp::resize_lp(self.lprec, rows, cols) }
.check_with(LpSolveError::ColumnOperationFailed {
column: None,
context: OpContext::Resize,
})
}
pub fn add_column(&mut self, values: &mut [f64]) -> Result<()> {
let expected = (self.num_rows() as usize).saturating_add(1);
if values.len() != expected {
return Err(LpSolveError::DimensionMismatch {
expected,
actual: values.len(),
context: OpContext::AddColumn,
});
}
unsafe { lp::add_column(self.lprec, values.as_mut_ptr()) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(self.num_cols() + 1),
context: OpContext::AddColumn,
})
}
pub fn add_column_scatter(&mut self, values: &mut [f64], indices: &mut [libc::c_int]) -> Result<()> {
if values.len() != indices.len() {
return Err(LpSolveError::DimensionMismatch {
expected: values.len(),
actual: indices.len(),
context: OpContext::AddColumn,
});
}
unsafe {
lp::add_columnex(
self.lprec,
values.len() as libc::c_int,
values.as_mut_ptr(),
indices.as_mut_ptr(),
)
}.check_with(LpSolveError::ColumnOperationFailed {
column: Some(self.num_cols() + 1),
context: OpContext::AddColumn,
})
}
pub fn get_column(&self, values: &mut [f64], column: libc::c_int) -> Result<()> {
self.check_buffer_and_bounds(
values.len(),
(self.num_rows() as usize).saturating_add(1),
column,
1,
self.num_cols(),
OpContext::GetColumn,
)?;
unsafe { lp::get_column(self.lprec, column, values.as_mut_ptr()) }
.check_with(LpSolveError::MatrixOperationFailed {
row: 0,
col: column,
op: error::MatrixOp::GetColumn,
})
}
pub fn get_row(&self, values: &mut [f64], row: libc::c_int) -> Result<()> {
self.check_buffer_and_bounds(
values.len(),
(self.num_cols() as usize).saturating_add(1),
row,
1,
self.num_rows(),
OpContext::GetRow,
)?;
unsafe { lp::get_row(self.lprec, row, values.as_mut_ptr()) }
.check_with(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::GetRow,
})
}
pub fn set_row(&mut self, row: libc::c_int, values: &mut [f64]) -> Result<()> {
self.check_buffer_and_bounds(
values.len(),
(self.num_cols() as usize).saturating_add(1),
row,
1,
self.num_rows(),
OpContext::SetRow,
)?;
unsafe { lp::set_row(self.lprec, row, values.as_mut_ptr()) }
.check_with(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::SetRow,
})
}
pub fn set_rowex(&mut self, row: libc::c_int, values: &mut [f64], indices: &mut [libc::c_int]) -> Result<()> {
if row < 1 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 1,
max: self.num_rows(),
context: OpContext::SetRow,
});
}
if values.len() != indices.len() {
return Err(LpSolveError::DimensionMismatch {
expected: values.len(),
actual: indices.len(),
context: OpContext::SetRow,
});
}
unsafe {
lp::set_rowex(
self.lprec,
row,
values.len() as libc::c_int,
values.as_mut_ptr(),
indices.as_mut_ptr(),
)
}.check_with(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::SetRow,
})
}
pub fn set_column(&mut self, col: libc::c_int, values: &mut [f64]) -> Result<()> {
self.check_buffer_and_bounds(
values.len(),
(self.num_rows() as usize).saturating_add(1),
col,
1,
self.num_cols(),
OpContext::SetColumn,
)?;
unsafe { lp::set_column(self.lprec, col, values.as_mut_ptr()) }
.check_with(LpSolveError::MatrixOperationFailed {
row: 0,
col,
op: error::MatrixOp::SetColumn,
})
}
pub fn set_columnex(
&mut self,
col: libc::c_int,
values: &mut [f64],
indices: &mut [libc::c_int],
) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetColumn,
});
}
if values.len() != indices.len() {
return Err(LpSolveError::DimensionMismatch {
expected: values.len(),
actual: indices.len(),
context: OpContext::SetColumn,
});
}
unsafe {
lp::set_columnex(
self.lprec,
col,
values.len() as libc::c_int,
values.as_mut_ptr(),
indices.as_mut_ptr(),
)
}.check_with(LpSolveError::MatrixOperationFailed {
row: 0,
col,
op: error::MatrixOp::SetColumn,
})
}
pub fn add_constraint(&mut self, coeffs: &mut [f64], target: f64, kind: ConstraintType) -> Result<()> {
let expected = (self.num_cols() as usize).saturating_add(1);
if coeffs.len() != expected {
return Err(LpSolveError::DimensionMismatch {
expected,
actual: coeffs.len(),
context: OpContext::AddConstraint,
});
}
unsafe {
lp::add_constraint(
self.lprec,
coeffs.as_mut_ptr(),
kind as libc::c_int,
target,
)
}.check_with(LpSolveError::ConstraintAdditionFailed { row: None })
}
pub fn add_sos_constraint(
&mut self,
name: &CStr,
sostype: SOSType,
priority: libc::c_int,
weights: &mut [f64],
variables: &mut [libc::c_int],
) -> Result<()> {
if weights.len() != variables.len() {
return Err(LpSolveError::DimensionMismatch {
expected: weights.len(),
actual: variables.len(),
context: OpContext::AddConstraint,
});
}
let result = unsafe {
lp::add_SOS(
self.lprec,
name.as_ptr() as *mut _,
sostype as libc::c_int,
priority,
weights.len() as libc::c_int,
variables.as_mut_ptr(),
weights.as_mut_ptr(),
)
};
if result == 0 {
Err(LpSolveError::SOSConstraintError)
} else {
Ok(())
}
}
pub fn del_column(&mut self, col: libc::c_int) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::DeleteColumn,
});
}
unsafe { lp::del_column(self.lprec, col) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::DeleteColumn,
})
}
pub fn del_constraint(&mut self, row: libc::c_int) -> Result<()> {
if row < 1 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 1,
max: self.num_rows(),
context: OpContext::DeleteRow,
});
}
unsafe { lp::del_constraint(self.lprec, row) }
.check_with(LpSolveError::ConstraintAdditionFailed {
row: Some(row),
})
}
pub fn is_negative(&self, col: libc::c_int) -> Result<bool> {
if col < 0 || col > self.num_cols() {
Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 0,
max: self.num_cols(),
context: OpContext::GetColumn,
})
} else {
Ok(unsafe { lp::is_negative(self.lprec, col) } == 1)
}
}
pub fn set_variable_type(&mut self, col: libc::c_int, vartype: VarType) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetVariableType,
});
}
unsafe { lp::set_binary(self.lprec, col, vartype as libc::c_uchar) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetVariableType,
})
}
pub fn set_binary(&mut self, col: libc::c_int, is_binary: bool) -> Result<()> {
self.set_variable_type(col, if is_binary { VarType::Binary } else { VarType::Float })
}
pub fn get_variable_type(&self, col: libc::c_int) -> Result<VarType> {
if col < 0 || col > self.num_cols() {
Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 0,
max: self.num_cols(),
context: OpContext::GetColumn,
})
} else {
let res = if unsafe { lp::is_binary(self.lprec, col) } == 1 {
VarType::Binary
} else {
VarType::Float
};
Ok(res)
}
}
pub fn set_bounds(&mut self, col: libc::c_int, lower: f64, upper: f64) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetBounds,
});
}
unsafe { lp::set_bounds(self.lprec, col, lower, upper) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetBounds,
})
}
pub fn set_bounds_mode(&mut self, tighten: BoundsMode) {
unsafe { lp::set_bounds_tighter(self.lprec, tighten as libc::c_uchar) };
}
pub fn get_bounds_mode(&self) -> BoundsMode {
if unsafe { lp::get_bounds_tighter(self.lprec) } == 1 {
BoundsMode::Restrictive
} else {
BoundsMode::None
}
}
pub fn set_constraint_type(&mut self, row: libc::c_int, contype: ConstraintType) -> Result<()> {
if row < 1 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 1,
max: self.num_rows(),
context: OpContext::SetRow,
});
}
unsafe { lp::set_constr_type(self.lprec, row, contype as libc::c_int) }
.check_with(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::SetRow,
})
}
pub fn get_constraint_type(&self, row: libc::c_int) -> Result<ConstraintType> {
if row < 0 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 0,
max: self.num_rows(),
context: OpContext::GetRow,
});
}
let res = unsafe { lp::get_constr_type(self.lprec, row) };
match res {
1 => Ok(ConstraintType::Le),
2 => Ok(ConstraintType::Ge),
3 => Ok(ConstraintType::Eq),
_ => Err(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::GetRow,
}),
}
}
pub fn set_unbounded(&mut self, col: libc::c_int) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetUnbounded,
});
}
unsafe { lp::set_unbounded(self.lprec, col) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetUnbounded,
})
}
pub fn is_unbounded(&self, col: libc::c_int) -> Result<bool> {
if col < 0 || col > self.num_cols() {
Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 0,
max: self.num_cols(),
context: OpContext::GetColumn,
})
} else {
Ok(unsafe { lp::is_unbounded(self.lprec, col) } == 1)
}
}
pub fn set_infinite(&mut self, infinity: f64) {
unsafe { lp::set_infinite(self.lprec, infinity) };
}
pub fn get_infinite(&self) -> f64 {
unsafe { lp::get_infinite(self.lprec) }
}
pub fn set_integer(&mut self, col: libc::c_int, must_be_integer: bool) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetInteger,
});
}
unsafe { lp::set_int(self.lprec, col, if must_be_integer { 1 } else { 0 }) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetInteger,
})
}
pub fn is_integer(&self, col: libc::c_int) -> Result<bool> {
if col < 0 || col > self.num_cols() {
Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 0,
max: self.num_cols(),
context: OpContext::GetColumn,
})
} else {
Ok(unsafe { lp::is_int(self.lprec, col) } == 1)
}
}
pub fn set_semicont(&mut self, col: libc::c_int, must_be_semicont: bool) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetSemicont,
});
}
unsafe { lp::set_semicont(self.lprec, col, if must_be_semicont { 1 } else { 0 }) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetSemicont,
})
}
pub fn is_semicont(&self, col: libc::c_int) -> Result<bool> {
if col < 0 || col > self.num_cols() {
Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 0,
max: self.num_cols(),
context: OpContext::GetColumn,
})
} else {
Ok(unsafe { lp::is_semicont(self.lprec, col) } == 1)
}
}
pub fn set_var_weights(&mut self, weights: &mut [f64]) -> Result<()> {
let required = (self.num_cols() as usize).saturating_add(1);
if weights.len() < required {
return Err(LpSolveError::BufferTooSmall {
required,
provided: weights.len(),
context: OpContext::SetColumn,
});
}
unsafe { lp::set_var_weights(self.lprec, weights.as_mut_ptr()) }
.check_with(LpSolveError::ColumnOperationFailed {
column: None,
context: OpContext::SetColumn,
})
}
pub fn get_var_priority(&self, col: libc::c_int) -> libc::c_int {
unsafe { lp::get_var_priority(self.lprec, col) }
}
pub fn is_sos_var(&self, col: libc::c_int) -> bool {
unsafe { lp::is_SOS_var(self.lprec, col) == 1 }
}
pub fn set_objective_function(&mut self, coeffs: &mut [f64]) -> Result<()> {
let expected = (self.num_cols() as usize).saturating_add(1);
if coeffs.len() != expected {
return Err(LpSolveError::DimensionMismatch {
expected,
actual: coeffs.len(),
context: OpContext::SetObjective,
});
}
unsafe { lp::set_obj_fn(self.lprec, coeffs.as_mut_ptr()) }
.check_with(LpSolveError::ObjectiveFunctionError)
}
pub fn scatter_objective_function(&mut self, coeffs: &mut [f64], indices: &mut [libc::c_int]) -> Result<()> {
if coeffs.len() != indices.len() {
return Err(LpSolveError::DimensionMismatch {
expected: coeffs.len(),
actual: indices.len(),
context: OpContext::SetObjective,
});
}
unsafe {
lp::set_obj_fnex(
self.lprec,
coeffs.len() as libc::c_int,
coeffs.as_mut_ptr(),
indices.as_mut_ptr(),
)
}.check_with(LpSolveError::ObjectiveFunctionError)
}
pub fn set_constraint_range(&mut self, row: libc::c_int, range: f64) -> Result<()> {
if row < 1 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 1,
max: self.num_rows(),
context: OpContext::SetRow,
});
}
unsafe { lp::set_rh_range(self.lprec, row, range) }
.check_with(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::SetRow,
})
}
pub fn get_constraint_range(&self, row: libc::c_int) -> Result<Option<f64>> {
if row < 0 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 0,
max: self.num_rows(),
context: OpContext::GetRow,
});
}
let delta = unsafe { lp::get_rh_range(self.lprec, row) };
if delta == self.get_infinite() {
Ok(None)
} else {
Ok(Some(delta))
}
}
pub fn set_rh(&mut self, row: libc::c_int, value: f64) -> Result<()> {
if row < 1 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 1,
max: self.num_rows(),
context: OpContext::SetRow,
});
}
unsafe { lp::set_rh(self.lprec, row, value) }
.check_with(LpSolveError::MatrixOperationFailed {
row,
col: 0,
op: error::MatrixOp::SetRow,
})
}
pub fn get_rh(&self, row: libc::c_int) -> Result<f64> {
if row < 1 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 1,
max: self.num_rows(),
context: OpContext::GetRow,
});
}
Ok(unsafe { lp::get_rh(self.lprec, row) })
}
pub fn set_timeout(&mut self, seconds: libc::c_long) {
unsafe { lp::set_timeout(self.lprec, seconds) };
}
pub fn get_timeout(&self) -> libc::c_long {
unsafe { lp::get_timeout(self.lprec) }
}
pub fn set_maximize(&mut self) {
unsafe { lp::set_maxim(self.lprec) };
}
pub fn set_minimize(&mut self) {
unsafe { lp::set_minim(self.lprec) };
}
pub fn is_maximize(&self) -> bool {
unsafe { lp::is_maxim(self.lprec) == 1 }
}
pub fn is_minimize(&self) -> bool {
!self.is_maximize()
}
pub fn solve(&mut self) -> SolveStatus {
use SolveStatus::*;
match unsafe { lp::solve(self.lprec) } {
-2 => OutOfMemory,
-1 => NotRun,
0 => Optimal,
1 => Suboptimal,
2 => Infeasible,
3 => Unbounded,
4 => Degenerate,
5 => NumericalFailure,
6 => UserAbort,
7 => Timeout,
9 => Presolved,
10 => ProcFail,
11 => ProcBreak,
12 => FeasibleFound,
13 => NoFeasibleFound,
status => panic!("unknown solve status {}", status),
}
}
pub fn get_solution_variables<'a>(&self, vars: &'a mut [f64]) -> Option<&'a mut [f64]> {
let cols = self.num_cols();
if vars.len() < cols as usize {
None
} else {
unsafe { lp::get_variables(self.lprec, vars.as_mut_ptr()) };
Some(&mut vars[..cols as usize])
}
}
pub fn get_objective(&self) -> f64 {
unsafe { lp::get_objective(self.lprec) }
}
pub fn get_total_iter(&self) -> libc::c_long {
unsafe { lp::get_total_iter(self.lprec) }
}
pub fn get_total_nodes(&self) -> libc::c_long {
unsafe { lp::get_total_nodes(self.lprec) }
}
pub fn time_elapsed(&self) -> f64 {
unsafe { lp::time_elapsed(self.lprec) }
}
pub fn get_dual_solution<'a>(&self, duals: &'a mut [f64]) -> Option<&'a mut [f64]> {
let rows = self.num_rows() as usize;
let cols = self.num_cols() as usize;
let total = rows.saturating_add(cols);
if duals.len() < total {
None
} else {
unsafe { lp::get_dual_solution(self.lprec, duals.as_mut_ptr()) };
Some(&mut duals[..total])
}
}
pub fn get_lambda<'a>(&self, lambda: &'a mut [f64]) -> Option<&'a mut [f64]> {
let rows = (self.num_rows() as usize).saturating_add(1);
if lambda.len() < rows {
None
} else {
unsafe { lp::get_lambda(self.lprec, lambda.as_mut_ptr()) };
Some(&mut lambda[..rows])
}
}
pub fn get_constraints<'a>(&self, constraints: &'a mut [f64]) -> Option<&'a mut [f64]> {
let rows = (self.num_rows() as usize).saturating_add(1);
if constraints.len() < rows {
None
} else {
unsafe { lp::get_constraints(self.lprec, constraints.as_mut_ptr()) };
Some(&mut constraints[..rows])
}
}
pub fn get_sensitivity_rhs(
&self,
duals: &mut [f64],
dualsfrom: &mut [f64],
dualstill: &mut [f64],
) -> Result<()> {
let required = (self.num_rows() as usize).saturating_add(1);
let context = OpContext::GetSensitivityRhs;
for (buffer_len, _name) in [(duals.len(), "duals"), (dualsfrom.len(), "dualsfrom"), (dualstill.len(), "dualstill")] {
if buffer_len < required {
return Err(LpSolveError::BufferTooSmall {
required,
provided: buffer_len,
context,
});
}
}
unsafe {
lp::get_sensitivity_rhs(
self.lprec,
duals.as_mut_ptr(),
dualsfrom.as_mut_ptr(),
dualstill.as_mut_ptr(),
)
}.check_with(LpSolveError::MatrixOperationFailed {
row: 0,
col: 0,
op: error::MatrixOp::Get,
})
}
pub fn get_sensitivity_obj(&self, objfrom: &mut [f64], objtill: &mut [f64]) -> Result<()> {
let required = (self.num_cols() as usize).saturating_add(1);
let context = OpContext::GetSensitivityObj;
for (buffer_len, _name) in [(objfrom.len(), "objfrom"), (objtill.len(), "objtill")] {
if buffer_len < required {
return Err(LpSolveError::BufferTooSmall {
required,
provided: buffer_len,
context,
});
}
}
unsafe {
lp::get_sensitivity_obj(self.lprec, objfrom.as_mut_ptr(), objtill.as_mut_ptr())
}.check_with(LpSolveError::MatrixOperationFailed {
row: 0,
col: 0,
op: error::MatrixOp::Get,
})
}
pub fn set_presolve(&mut self, mode: PresolveMode) {
unsafe { lp::set_presolve(self.lprec, mode.bits(), -1) };
}
pub fn get_presolve(&self) -> PresolveMode {
let bits = unsafe { lp::get_presolve(self.lprec) };
PresolveMode::from_bits_truncate(bits)
}
pub fn set_scaling(&mut self, mode: ScalingMode) {
unsafe { lp::set_scaling(self.lprec, mode.bits()) };
}
pub fn get_scaling(&self) -> ScalingMode {
let bits = unsafe { lp::get_scaling(self.lprec) };
ScalingMode::from_bits_truncate(bits)
}
pub fn set_simplex_type(&mut self, simplex_type: SimplexType) {
unsafe { lp::set_simplextype(self.lprec, simplex_type as libc::c_int) };
}
pub fn get_simplex_type(&self) -> libc::c_int {
unsafe { lp::get_simplextype(self.lprec) }
}
pub fn set_prefer_dual(&mut self, prefer_dual: bool) {
unsafe { lp::set_preferdual(self.lprec, if prefer_dual { 1 } else { 0 }) };
}
pub fn set_pivoting(&mut self, rule: PivotRule) {
unsafe { lp::set_pivoting(self.lprec, rule as libc::c_int) };
}
pub fn get_pivoting(&self) -> PivotRule {
let value = unsafe { lp::get_pivoting(self.lprec) };
match value {
0 => PivotRule::FirstIndex,
1 => PivotRule::SteepestEdge,
2 => PivotRule::Devex,
3 => PivotRule::Random,
_ => PivotRule::FirstIndex, }
}
pub fn set_epsint(&mut self, epsint: f64) {
unsafe { lp::set_epsint(self.lprec, epsint) };
}
pub fn get_epsint(&self) -> f64 {
unsafe { lp::get_epsint(self.lprec) }
}
pub fn set_epsb(&mut self, epsb: f64) {
unsafe { lp::set_epsb(self.lprec, epsb) };
}
pub fn get_epsb(&self) -> f64 {
unsafe { lp::get_epsb(self.lprec) }
}
pub fn set_epsd(&mut self, epsd: f64) {
unsafe { lp::set_epsd(self.lprec, epsd) };
}
pub fn get_epsd(&self) -> f64 {
unsafe { lp::get_epsd(self.lprec) }
}
pub fn set_epspivot(&mut self, epspivot: f64) {
unsafe { lp::set_epspivot(self.lprec, epspivot) };
}
pub fn get_epspivot(&self) -> f64 {
unsafe { lp::get_epspivot(self.lprec) }
}
pub fn set_mip_gap(&mut self, absolute: bool, gap: f64) {
unsafe { lp::set_mip_gap(self.lprec, if absolute { 1 } else { 0 }, gap) };
}
pub fn get_mip_gap(&self, absolute: bool) -> f64 {
unsafe { lp::get_mip_gap(self.lprec, if absolute { 1 } else { 0 }) }
}
pub fn set_bb_rule(&mut self, rule: BranchRule) {
unsafe { lp::set_bb_rule(self.lprec, rule as libc::c_int) };
}
pub fn get_bb_rule(&self) -> BranchRule {
let value = unsafe { lp::get_bb_rule(self.lprec) };
match value {
0 => BranchRule::Ceiling,
1 => BranchRule::Floor,
2 => BranchRule::Automatic,
_ => BranchRule::Ceiling, }
}
pub fn set_bb_depth_limit(&mut self, depth: libc::c_int) {
unsafe { lp::set_bb_depthlimit(self.lprec, depth) };
}
pub fn get_bb_depth_limit(&self) -> libc::c_int {
unsafe { lp::get_bb_depthlimit(self.lprec) }
}
pub fn set_verbose(&mut self, level: Verbosity) {
unsafe { lp::set_verbose(self.lprec, level as libc::c_int) };
}
pub fn get_verbose(&self) -> libc::c_int {
unsafe { lp::get_verbose(self.lprec) }
}
pub fn default_basis(&mut self) {
unsafe { lp::default_basis(self.lprec) };
}
pub fn reset_basis(&mut self) {
unsafe { lp::reset_basis(self.lprec) };
}
pub fn read_basis(&mut self, filename: &CStr) -> Result<()> {
unsafe {
lp::read_basis(
self.lprec,
filename.as_ptr() as *mut _,
std::ptr::null_mut(),
)
}.check_with(LpSolveError::BasisOperationFailed)
}
pub fn write_basis(&self, filename: &CStr) -> Result<()> {
unsafe { lp::write_basis(self.lprec, filename.as_ptr() as *mut _) }
.check_with(LpSolveError::BasisOperationFailed)
}
pub fn set_basiscrash(&mut self, mode: BasisCrash) {
unsafe { lp::set_basiscrash(self.lprec, mode as libc::c_int) };
}
pub fn get_basiscrash(&self) -> BasisCrash {
let value = unsafe { lp::get_basiscrash(self.lprec) };
match value {
0 => BasisCrash::None,
1 => BasisCrash::MostFeasible,
2 => BasisCrash::LeastDegen,
_ => BasisCrash::None, }
}
pub fn set_add_rowmode(&mut self, enabled: bool) -> Result<()> {
unsafe { lp::set_add_rowmode(self.lprec, if enabled { 1 } else { 0 }) }
.check_with(LpSolveError::InAddRowMode)
}
pub fn is_add_rowmode(&self) -> bool {
unsafe { lp::is_add_rowmode(self.lprec) == 1 }
}
pub fn set_upbo(&mut self, col: libc::c_int, value: f64) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetUpbo,
});
}
unsafe { lp::set_upbo(self.lprec, col, value) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetUpbo,
})
}
pub fn get_upbo(&self, col: libc::c_int) -> Result<f64> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::GetColumn,
});
}
Ok(unsafe { lp::get_upbo(self.lprec, col) })
}
pub fn set_lowbo(&mut self, col: libc::c_int, value: f64) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetLowbo,
});
}
unsafe { lp::set_lowbo(self.lprec, col, value) }
.check_with(LpSolveError::ColumnOperationFailed {
column: Some(col),
context: OpContext::SetLowbo,
})
}
pub fn get_lowbo(&self, col: libc::c_int) -> Result<f64> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::GetColumn,
});
}
Ok(unsafe { lp::get_lowbo(self.lprec, col) })
}
pub fn get_mat(&self, row: libc::c_int, col: libc::c_int) -> Result<f64> {
if row < 0 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 0,
max: self.num_rows(),
context: OpContext::MatrixGet,
});
}
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::MatrixGet,
});
}
Ok(unsafe { lp::get_mat(self.lprec, row, col) })
}
pub fn set_mat(&mut self, row: libc::c_int, col: libc::c_int, value: f64) -> Result<()> {
if row < 0 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 0,
max: self.num_rows(),
context: OpContext::MatrixSet,
});
}
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::MatrixSet,
});
}
unsafe { lp::set_mat(self.lprec, row, col, value) }
.check_with(LpSolveError::MatrixOperationFailed {
row,
col,
op: error::MatrixOp::Set,
})
}
pub fn get_nonzeros(&self) -> libc::c_int {
unsafe { lp::get_nonzeros(self.lprec) }
}
pub fn is_feasible(&self, values: &mut [f64], threshold: f64) -> Result<bool> {
let required = (self.num_cols() as usize).saturating_add(1);
if values.len() != required {
return Err(LpSolveError::DimensionMismatch {
expected: required,
actual: values.len(),
context: OpContext::Validate,
});
}
Ok(unsafe { lp::is_feasible(self.lprec, values.as_mut_ptr(), threshold) == 1 })
}
pub fn bin_count(&self, working: bool) -> libc::c_int {
unsafe { lp::bin_count(self.lprec, if working { 1 } else { 0 }) }
}
pub fn mip_count(&self) -> libc::c_int {
unsafe { lp::MIP_count(self.lprec) }
}
pub fn sos_count(&self) -> libc::c_int {
unsafe { lp::SOS_count(self.lprec) }
}
pub fn set_anti_degen(&mut self, strategy: AntiDegen) {
unsafe { lp::set_anti_degen(self.lprec, strategy.bits()) };
}
pub fn get_anti_degen(&self) -> AntiDegen {
let bits = unsafe { lp::get_anti_degen(self.lprec) };
AntiDegen::from_bits_truncate(bits)
}
pub fn set_improve(&mut self, mode: ImproveMode) {
unsafe { lp::set_improve(self.lprec, mode.bits()) };
}
pub fn get_improve(&self) -> ImproveMode {
let bits = unsafe { lp::get_improve(self.lprec) };
ImproveMode::from_bits_truncate(bits)
}
pub fn set_obj_bound(&mut self, bound: f64) {
unsafe { lp::set_obj_bound(self.lprec, bound) };
}
pub fn get_obj_bound(&self) -> f64 {
unsafe { lp::get_obj_bound(self.lprec) }
}
pub fn set_break_at_value(&mut self, value: f64) {
unsafe { lp::set_break_at_value(self.lprec, value) };
}
pub fn get_break_at_value(&self) -> f64 {
unsafe { lp::get_break_at_value(self.lprec) }
}
pub fn set_break_at_first(&mut self, break_at_first: bool) {
unsafe { lp::set_break_at_first(self.lprec, if break_at_first { 1 } else { 0 }) };
}
pub fn is_break_at_first(&self) -> bool {
unsafe { lp::is_break_at_first(self.lprec) == 1 }
}
pub fn unscale(&mut self) {
unsafe { lp::unscale(self.lprec) };
}
pub unsafe fn from_lprec(lprec: *mut lp::lprec) -> Problem {
debug_assert!(!lprec.is_null(), "from_lprec called with null pointer");
Problem { lprec }
}
pub fn to_lprec(&self) -> *mut lp::lprec {
self.lprec
}
pub fn into_lprec(self) -> *mut lp::lprec {
let ptr = self.lprec;
std::mem::forget(self); ptr
}
pub fn num_cols(&self) -> libc::c_int {
unsafe { lp::get_Ncolumns(self.lprec) }
}
pub fn num_rows(&self) -> libc::c_int {
unsafe { lp::get_Nrows(self.lprec) }
}
pub fn set_row_name(&mut self, row: libc::c_int, name: &CStr) -> Result<()> {
if row < 0 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 0,
max: self.num_rows(),
context: OpContext::SetRowName,
});
}
unsafe { lp::set_row_name(self.lprec, row, name.as_ptr() as *mut _) }
.check_with(LpSolveError::NamingError {
entity: EntityType::Row,
index: row,
})
}
pub fn get_row_name(&self, row: libc::c_int) -> Result<Option<CString>> {
if row < 0 || row > self.num_rows() {
return Err(LpSolveError::IndexOutOfBounds {
index: row,
min: 0,
max: self.num_rows(),
context: OpContext::GetRow,
});
}
let ptr = unsafe { lp::get_row_name(self.lprec, row) };
if ptr.is_null() {
Ok(None)
} else {
Ok(unsafe { Some(CStr::from_ptr(ptr).to_owned()) })
}
}
pub fn set_col_name(&mut self, col: libc::c_int, name: &CStr) -> Result<()> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::SetColName,
});
}
unsafe { lp::set_col_name(self.lprec, col, name.as_ptr() as *mut _) }
.check_with(LpSolveError::NamingError {
entity: EntityType::Column,
index: col,
})
}
pub fn get_col_name(&self, col: libc::c_int) -> Result<Option<CString>> {
if col < 1 || col > self.num_cols() {
return Err(LpSolveError::IndexOutOfBounds {
index: col,
min: 1,
max: self.num_cols(),
context: OpContext::GetColumn,
});
}
let ptr = unsafe { lp::get_col_name(self.lprec, col) };
if ptr.is_null() {
Ok(None)
} else {
Ok(unsafe { Some(CStr::from_ptr(ptr).to_owned()) })
}
}
pub fn set_problem_name(&mut self, name: &CStr) -> Result<()> {
unsafe { lp::set_lp_name(self.lprec, name.as_ptr() as *mut _) }
.check_with(LpSolveError::NamingError {
entity: EntityType::Problem,
index: 0,
})
}
pub fn get_problem_name(&self) -> Result<Option<CString>> {
let ptr = unsafe { lp::get_lp_name(self.lprec) };
if ptr.is_null() {
Ok(None)
} else {
Ok(unsafe { Some(CStr::from_ptr(ptr).to_owned()) })
}
}
}
impl Drop for Problem {
fn drop(&mut self) {
unsafe { lp::delete_lp(self.lprec) }
}
}
impl Clone for Problem {
fn clone(&self) -> Problem {
self.try_clone().expect("OOM when trying to copy_lp")
}
}
impl Problem {
pub fn try_clone(&self) -> Result<Problem> {
let ptr = unsafe { lp::copy_lp(self.lprec) };
if ptr.is_null() {
Err(LpSolveError::OutOfMemory)
} else {
Ok(Problem { lprec: ptr })
}
}
}
unsafe impl Send for Problem {}
#[cfg(test)]
mod tests {
use crate::{Problem, SolveStatus};
#[test]
fn smoke() {
let mut lp = Problem::new(0, 0).unwrap();
assert_eq!(lp.solve(), SolveStatus::NotRun);
}
}