use crate::row::BasisStatus;
use crate::row::Row;
use crate::scip::ScipPtr;
use crate::{Variable, ffi};
use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct Col {
pub(crate) raw: *mut ffi::SCIP_COL,
pub(crate) scip: Rc<ScipPtr>,
}
impl Col {
pub fn inner(&self) -> *mut ffi::SCIP_COL {
self.raw
}
pub fn index(&self) -> usize {
let id = unsafe { ffi::SCIPcolGetIndex(self.raw) };
assert!(id >= 0);
id as usize
}
pub fn obj(&self) -> f64 {
unsafe { ffi::SCIPcolGetObj(self.raw) }
}
pub fn lb(&self) -> f64 {
unsafe { ffi::SCIPcolGetLb(self.raw) }
}
pub fn ub(&self) -> f64 {
unsafe { ffi::SCIPcolGetUb(self.raw) }
}
pub fn best_bound(&self) -> f64 {
unsafe { ffi::SCIPcolGetBestBound(self.raw) }
}
pub fn var(&self) -> Variable {
let var_ptr = unsafe { ffi::SCIPcolGetVar(self.raw) };
Variable {
raw: var_ptr,
scip: Rc::clone(&self.scip),
}
}
pub fn primal_sol(&self) -> f64 {
unsafe { ffi::SCIPcolGetPrimsol(self.raw) }
}
pub fn min_primal_sol(&self) -> f64 {
unsafe { ffi::SCIPcolGetMinPrimsol(self.raw) }
}
pub fn max_primal_sol(&self) -> f64 {
unsafe { ffi::SCIPcolGetMaxPrimsol(self.raw) }
}
pub fn basis_status(&self) -> BasisStatus {
unsafe { ffi::SCIPcolGetBasisStatus(self.raw) }.into()
}
pub fn var_probindex(&self) -> Option<usize> {
let probindex = unsafe { ffi::SCIPcolGetVarProbindex(self.raw) };
if probindex < 0 {
None
} else {
Some(probindex as usize)
}
}
pub fn is_integral(&self) -> bool {
(unsafe { ffi::SCIPcolIsIntegral(self.raw) }) != 0
}
pub fn is_removable(&self) -> bool {
(unsafe { ffi::SCIPcolIsRemovable(self.raw) }) != 0
}
pub fn lp_pos(&self) -> Option<usize> {
let pos = unsafe { ffi::SCIPcolGetLPPos(self.raw) };
if pos < 0 { None } else { Some(pos as usize) }
}
pub fn lp_depth(&self) -> Option<usize> {
let depth = unsafe { ffi::SCIPcolGetLPDepth(self.raw) };
if depth < 0 {
None
} else {
Some(depth as usize)
}
}
pub fn is_in_lp(&self) -> bool {
(unsafe { ffi::SCIPcolIsInLP(self.raw) }) != 0
}
pub fn n_non_zeros(&self) -> usize {
let n_non_zeros = unsafe { ffi::SCIPcolGetNNonz(self.raw) };
assert!(n_non_zeros >= 0);
n_non_zeros as usize
}
pub fn n_lp_non_zeros(&self) -> usize {
let n_lp_non_zeros = unsafe { ffi::SCIPcolGetNLPNonz(self.raw) };
assert!(n_lp_non_zeros >= 0);
n_lp_non_zeros as usize
}
pub fn rows(&self) -> Vec<Row> {
let n_non_zeros = self.n_non_zeros();
let rows_ptr = unsafe { ffi::SCIPcolGetRows(self.raw) };
let rows = unsafe { std::slice::from_raw_parts(rows_ptr, n_non_zeros) };
rows.iter()
.map(|&row_ptr| Row {
raw: row_ptr,
scip: Rc::clone(&self.scip),
})
.collect()
}
pub fn vals(&self) -> Vec<f64> {
let n_non_zeros = self.n_non_zeros();
let vals_ptr = unsafe { ffi::SCIPcolGetVals(self.raw) };
let vals = unsafe { std::slice::from_raw_parts(vals_ptr, n_non_zeros) };
vals.to_vec()
}
pub fn strong_branching_node(&self) -> Option<i64> {
let node = unsafe { ffi::SCIPcolGetStrongbranchNode(self.raw) };
if node < 0 { None } else { Some(node) }
}
pub fn n_strong_branches(&self) -> usize {
let n_strong_branches = unsafe { ffi::SCIPcolGetNStrongbranchs(self.raw) };
assert!(n_strong_branches >= 0);
n_strong_branches as usize
}
pub fn age(&self) -> usize {
let age = unsafe { ffi::SCIPcolGetAge(self.raw) };
assert!(age >= 0);
age as usize
}
pub fn redcost(&self) -> f64 {
unsafe { ffi::SCIPgetColRedcost(self.scip.raw, self.raw) }
}
}
impl PartialEq for Col {
fn eq(&self, other: &Self) -> bool {
self.index() == other.index() && self.raw == other.raw
}
}
#[cfg(test)]
mod tests {
use crate::prelude::eventhdlr;
use crate::{
BasisStatus, Event, EventMask, Eventhdlr, Model, ModelWithProblem, ObjSense, ParamSetting,
ProblemOrSolving, SCIPEventhdlr, Solving, VarType,
};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
struct ColTesterEventHandler {
checked: Rc<AtomicBool>,
}
impl Eventhdlr for ColTesterEventHandler {
fn get_type(&self) -> EventMask {
EventMask::FIRST_LP_SOLVED
}
fn execute(&mut self, model: Model<Solving>, _eventhdlr: SCIPEventhdlr, event: Event) {
assert_eq!(event.event_type(), EventMask::FIRST_LP_SOLVED);
let vars = model.vars();
let Some(col) = vars[0].col() else { return };
assert_eq!(col.index(), 0);
assert_eq!(col.obj(), -3.0);
assert_eq!(col.lb(), 0.0);
assert_eq!(col.ub(), 50.0);
assert_eq!(col.best_bound(), 50.0);
assert_eq!(col.primal_sol(), 40.0);
assert_eq!(col.min_primal_sol(), 40.0);
assert_eq!(col.max_primal_sol(), 40.0);
assert_eq!(col.basis_status(), BasisStatus::Basic);
assert_eq!(col.var_probindex(), Some(0));
assert!(!col.is_integral());
assert!(!col.is_removable());
assert_eq!(col.lp_pos(), Some(0));
assert_eq!(col.lp_depth(), Some(0));
assert!(col.is_in_lp());
assert_eq!(col.n_non_zeros(), 2);
assert_eq!(col.n_lp_non_zeros(), 2);
assert_eq!(col.vals(), vec![2.0, 1.0]);
assert_eq!(col.strong_branching_node(), None);
assert_eq!(col.n_strong_branches(), 0);
assert_eq!(col.age(), 0);
self.checked.store(true, Ordering::SeqCst);
}
}
fn col_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
}
#[test]
fn test_col() {
let checked = Rc::new(AtomicBool::new(false));
let mut model = col_test_model();
model.add(
eventhdlr(ColTesterEventHandler {
checked: checked.clone(),
})
.name("ColTesterEventHandler"),
);
model.solve();
assert!(
checked.load(Ordering::SeqCst),
"column assertions never ran"
);
}
}