Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 74 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
//! ```

use std::convert::{TryFrom, TryInto};
use std::ffi::{c_void, CString};
use std::ffi::{c_void, CStr, CString};
use std::num::{NonZeroU32, TryFromIntError};
use std::ops::{Bound, Index, RangeBounds};
use std::os::raw::c_int;
Expand Down Expand Up @@ -969,25 +969,17 @@ impl SolvedModel {
/// The mip gap of the solution. Should be 0.0 if an optimal solution was
/// found. Will be INFINITY if no variables have an integer constraint.
pub fn mip_gap(&self) -> f64 {
let name = CString::new("mip_gap").unwrap();
let gap: &mut f64 = &mut -1.0;
let status =
unsafe { Highs_getDoubleInfoValue(self.highs.unsafe_mut_ptr(), name.as_ptr(), gap) };
try_handle_status(status, "Highs_getDoubleInfoValue")
.map(|_| *gap)
.unwrap()
self.double_info_value(c"mip_gap")
.expect("mip_gap is a known double info key")
}

/// The primal solution status, reflecting if a solution was found or not.
pub fn primal_solution_status(&self) -> HighsSolutionStatus {
let name = CString::new("primal_solution_status").unwrap();
let solution_status: &mut HighsInt = &mut -1;
let status = unsafe {
Highs_getIntInfoValue(self.highs.unsafe_mut_ptr(), name.as_ptr(), solution_status)
};
try_handle_status(status, "Highs_getIntInfoValue")
.map(|_| HighsSolutionStatus::try_from(*solution_status).unwrap())
.unwrap()
let value = self
.int_info_value(c"primal_solution_status")
.expect("primal_solution_status is a known HighsInt info key");
HighsSolutionStatus::try_from(value as HighsInt)
.expect("HiGHS returned an unrecognized primal solution status")
}

/// Get the solution to the problem
Expand Down Expand Up @@ -1018,6 +1010,72 @@ impl SolvedModel {
}
}

/// Read a `HighsInt`-typed solution info value by name and widen it to
/// `i64`.
///
/// `name` must be a key that HiGHS exposes as a `HighsInt`-typed info value.
///
/// # Errors
///
/// Returns the failing [`HighsStatus`] when `name` is not a known
/// `HighsInt`-typed info key.
pub fn int_info_value(&self, name: &CStr) -> Result<i64, HighsStatus> {
let value: &mut HighsInt = &mut -1;
let status =
unsafe { Highs_getIntInfoValue(self.highs.unsafe_mut_ptr(), name.as_ptr(), value) };
try_handle_status(status, "Highs_getIntInfoValue").map(|_| i64::from(*value))
}

/// Read a `double`-typed solution info value by name.
///
/// `name` must be a key that HiGHS exposes as a `double`-typed info value.
///
/// # Errors
///
/// Returns the failing [`HighsStatus`] when `name` is not a known
/// `double`-typed info key.
pub fn double_info_value(&self, name: &CStr) -> Result<f64, HighsStatus> {
let value: &mut f64 = &mut -1.0;
let status =
unsafe { Highs_getDoubleInfoValue(self.highs.unsafe_mut_ptr(), name.as_ptr(), value) };
try_handle_status(status, "Highs_getDoubleInfoValue").map(|_| *value)
}
Comment on lines +1024 to +1042

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this fail in normal operations ? if yes, then return a Result. Otherwise, use expect instead of unwrap, and explain why this can't fail clearly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highs_getIntInfoValue returns kError in only two cases, an unknown info name or a name whose info isn't HighsInt-typed. Both depend only on the name argument. Every caller passes a valid HighsInt key (c"simplex_iteration_count", etc.), so the only way to trigger it is a code change that introduces a bad literal.

I based this accessor based on others in the library, for example mip_gap, primal_solution_status. Should we also switch to an expect there or is it fine as it is?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the safety boundary is in the callers, let's put the .expect(...) there. int_info_value can be public, return a Result, and document when it returns an error. The accessor functions can be trusted and not return a Result because the parameter they pass is trusted

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and extended the same to the existing mip_gap()/primal_solution_status().


/// The number of simplex iterations performed for this solution
/// (`0` when the interior-point method was not used).
pub fn simplex_iteration_count(&self) -> i64 {
self.int_info_value(c"simplex_iteration_count")
.expect("simplex_iteration_count is a known HighsInt info key")
}

/// The number of interior-point (IPM) iterations performed for this solution
/// (`0` when the interior-point method was not used).
pub fn ipm_iteration_count(&self) -> i64 {
self.int_info_value(c"ipm_iteration_count")
.expect("ipm_iteration_count is a known HighsInt info key")
}

/// The number of QP solver iterations performed for this solution (`0` when
/// the model was not a quadratic program).
pub fn qp_iteration_count(&self) -> i64 {
self.int_info_value(c"qp_iteration_count")
.expect("qp_iteration_count is a known HighsInt info key")
}

/// The number of first-order (PDLP) iterations performed for this solution
/// (`0` when the PDLP solver was not used).
pub fn pdlp_iteration_count(&self) -> i64 {
self.int_info_value(c"pdlp_iteration_count")
.expect("pdlp_iteration_count is a known HighsInt info key")
}

/// The number of crossover iterations performed for this solution
/// (`0` when no crossover ran).
pub fn crossover_iteration_count(&self) -> i64 {
self.int_info_value(c"crossover_iteration_count")
.expect("crossover_iteration_count is a known HighsInt info key")
}

/// Number of variables
fn num_cols(&self) -> usize {
self.highs.num_cols().expect("invalid number of columns")
Expand Down
Loading