use crate::scip::ScipPtr;
use crate::{Col, Constraint, Variable, ffi};
use std::ffi::c_int;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct Row {
pub(crate) raw: *mut ffi::SCIP_ROW,
pub(crate) scip: Rc<ScipPtr>,
}
impl Row {
pub fn inner(&self) -> *mut ffi::SCIP_ROW {
self.raw
}
pub fn n_non_zeroes(&self) -> usize {
let len = unsafe { ffi::SCIProwGetNNonz(self.raw) };
assert!(len >= 0);
len as usize
}
pub fn cols(&self) -> Vec<Col> {
let mut columns = Vec::new();
let cols_ptr = unsafe { ffi::SCIProwGetCols(self.raw) };
for i in 0..self.n_non_zeroes() {
let col_ptr = unsafe { *cols_ptr.add(i) };
let col = Col {
raw: col_ptr,
scip: Rc::clone(&self.scip),
};
columns.push(col);
}
columns
}
pub fn index(&self) -> usize {
let id = unsafe { ffi::SCIProwGetIndex(self.raw) };
assert!(id >= 0);
id as usize
}
pub fn lhs(&self) -> f64 {
unsafe { ffi::SCIProwGetLhs(self.raw) }
}
pub fn rhs(&self) -> f64 {
unsafe { ffi::SCIProwGetRhs(self.raw) }
}
pub fn dual(&self) -> f64 {
unsafe { ffi::SCIProwGetDualsol(self.raw) }
}
pub fn farkas_dual(&self) -> f64 {
unsafe { ffi::SCIProwGetDualfarkas(self.raw) }
}
pub fn basis_status(&self) -> BasisStatus {
let status = unsafe { ffi::SCIProwGetBasisStatus(self.raw) };
status.into()
}
pub fn name(&self) -> String {
let name = unsafe { ffi::SCIProwGetName(self.raw) };
let name = unsafe { std::ffi::CStr::from_ptr(name) };
name.to_str().unwrap().to_string()
}
pub fn age(&self) -> usize {
let age = unsafe { ffi::SCIProwGetAge(self.raw) };
assert!(age >= 0);
age as usize
}
pub fn rank(&self) -> usize {
let rank = unsafe { ffi::SCIProwGetRank(self.raw) };
assert!(rank >= 0);
rank as usize
}
pub fn is_local(&self) -> bool {
(unsafe { ffi::SCIProwIsLocal(self.raw) }) != 0
}
pub fn is_modifiable(&self) -> bool {
(unsafe { ffi::SCIProwIsModifiable(self.raw) }) != 0
}
pub fn is_removable(&self) -> bool {
(unsafe { ffi::SCIProwIsRemovable(self.raw) }) != 0
}
pub fn is_integral(&self) -> bool {
(unsafe { ffi::SCIProwIsIntegral(self.raw) }) != 0
}
pub fn origin_type(&self) -> RowOrigin {
let origin = unsafe { ffi::SCIProwGetOrigintype(self.raw) };
origin.into()
}
pub fn constraint(&self) -> Option<Constraint> {
let cons_ptr = unsafe { ffi::SCIProwGetOriginCons(self.raw) };
if cons_ptr.is_null() {
None
} else {
let cons = Constraint {
raw: cons_ptr,
scip: Rc::clone(&self.scip),
};
Some(cons)
}
}
pub fn is_in_global_cut_pool(&self) -> bool {
(unsafe { ffi::SCIProwIsInGlobalCutpool(self.raw) }) != 0
}
pub fn is_in_lp(&self) -> bool {
(unsafe { ffi::SCIProwIsInLP(self.raw) }) != 0
}
pub fn lp_position(&self) -> Option<usize> {
if self.is_in_lp() {
let pos = unsafe { ffi::SCIProwGetLPPos(self.raw) };
Some(pos as usize)
} else {
None
}
}
pub fn depth(&self) -> usize {
let depth = unsafe { ffi::SCIProwGetLPDepth(self.raw) };
assert!(depth >= 0);
depth as usize
}
pub fn active_lp_count(&self) -> usize {
let count = unsafe { ffi::SCIProwGetActiveLPCount(self.raw) };
assert!(count >= 0);
count as usize
}
pub fn n_lp_since_create(&self) -> usize {
let count = unsafe { ffi::SCIProwGetNLPsAfterCreation(self.raw) };
assert!(count >= 0);
count as usize
}
pub fn set_rank(&mut self, rank: usize) {
unsafe { ffi::SCIProwChgRank(self.raw, rank as c_int) };
}
pub fn set_coeff(&mut self, var: &Variable, coeff: f64) {
unsafe { ffi::SCIPaddVarToRow(self.scip.raw, self.raw, var.raw, coeff) };
}
}
impl PartialEq for Row {
fn eq(&self, other: &Self) -> bool {
self.index() == other.index() && self.raw == other.raw
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum BasisStatus {
Lower,
Basic,
Upper,
Zero,
}
impl From<ffi::SCIP_BASESTAT> for BasisStatus {
fn from(status: ffi::SCIP_BASESTAT) -> Self {
match status {
ffi::SCIP_BaseStat_SCIP_BASESTAT_LOWER => BasisStatus::Lower,
ffi::SCIP_BaseStat_SCIP_BASESTAT_BASIC => BasisStatus::Basic,
ffi::SCIP_BaseStat_SCIP_BASESTAT_UPPER => BasisStatus::Upper,
ffi::SCIP_BaseStat_SCIP_BASESTAT_ZERO => BasisStatus::Zero,
_ => panic!("Unknown basis status"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum RowOrigin {
ConsHandler,
Constraint,
Reoptimization,
Separator,
Unspecified,
}
impl From<ffi::SCIP_ROWORIGINTYPE> for RowOrigin {
fn from(origin: ffi::SCIP_ROWORIGINTYPE) -> Self {
match origin {
ffi::SCIP_RowOriginType_SCIP_ROWORIGINTYPE_CONSHDLR => RowOrigin::ConsHandler,
ffi::SCIP_RowOriginType_SCIP_ROWORIGINTYPE_CONS => RowOrigin::Constraint,
ffi::SCIP_RowOriginType_SCIP_ROWORIGINTYPE_REOPT => RowOrigin::Reoptimization,
ffi::SCIP_RowOriginType_SCIP_ROWORIGINTYPE_SEPA => RowOrigin::Separator,
ffi::SCIP_RowOriginType_SCIP_ROWORIGINTYPE_UNSPEC => RowOrigin::Unspecified,
_ => panic!("Unknown row origin type"),
}
}
}
#[cfg(test)]
mod tests {
use crate::prelude::eventhdlr;
use crate::{Event, ProblemOrSolving};
use crate::{
EventMask, Eventhdlr, Model, ModelWithProblem, ObjSense, ParamSetting, Solving, VarType,
};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_row() {
struct RowTesterEventHandler {
checked: Rc<AtomicBool>,
}
impl Eventhdlr for RowTesterEventHandler {
fn get_type(&self) -> EventMask {
EventMask::FIRST_LP_SOLVED
}
fn execute(
&mut self,
model: Model<Solving>,
_eventhdlr: crate::SCIPEventhdlr,
_event: Event,
) {
let first_cons = model.conss()[0].clone();
let Some(mut row) = first_cons.row() else {
return;
};
let infinity = unsafe { crate::ffi::SCIPinfinity(model.scip.raw) };
assert_eq!(row.n_non_zeroes(), 2);
assert!(row.lhs() <= -infinity);
assert_eq!(row.rhs(), 100.0);
assert_eq!(row.index(), 0);
assert!(!row.is_modifiable());
assert!(!row.is_removable());
assert!(!row.is_local());
assert!(!row.is_integral());
assert!(row.constraint().is_some());
assert_eq!(row.basis_status(), crate::BasisStatus::Upper);
assert_eq!(row.origin_type(), crate::RowOrigin::Constraint);
assert!(!row.is_in_global_cut_pool());
assert!(row.is_in_lp());
assert_eq!(row.lp_position(), Some(0));
assert_eq!(row.depth(), 0);
assert_eq!(row.active_lp_count(), 1);
assert_eq!(row.n_lp_since_create(), 1);
assert_eq!(row.rank(), 0);
row.set_rank(1);
assert_eq!(row.rank(), 1);
assert_eq!(row.name(), "c1");
assert_eq!(row.age(), 0);
assert!((row.dual() - (-2.0 / 3.0)).abs() < 1e-9);
assert!(row.farkas_dual() >= infinity);
let cols = row.cols();
assert_eq!(cols.len(), 2);
assert_eq!(cols[0].index(), 0);
self.checked.store(true, Ordering::SeqCst);
}
}
let checked = Rc::new(AtomicBool::new(false));
let mut model = row_test_model();
model.add(
eventhdlr(RowTesterEventHandler {
checked: checked.clone(),
})
.name("RowTesterEventHandler"),
);
model.solve();
assert!(checked.load(Ordering::SeqCst), "row assertions never ran");
}
fn row_test_model() -> Model<crate::ProblemCreated> {
let mut model = Model::new()
.hide_output()
.include_default_plugins()
.create_prob("test")
.set_obj_sense(ObjSense::Maximize)
.set_presolving(ParamSetting::Off)
.set_separating(ParamSetting::Off)
.set_heuristics(ParamSetting::Off);
let x1 = model.add_var(0.0, f64::INFINITY, 3.0, "x1", VarType::Continuous);
let x2 = model.add_var(0.0, f64::INFINITY, 4.0, "x2", VarType::Continuous);
model.add_cons(vec![&x1, &x2], &[2.0, 1.0], f64::NEG_INFINITY, 100.0, "c1");
model.add_cons(vec![&x1, &x2], &[1.0, 2.0], f64::NEG_INFINITY, 80.0, "c2");
model
}
}