#![allow(clippy::float_cmp)]
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::interner::NameId;
#[cfg_attr(feature = "diff", derive(diff::Diff), diff(attr(#[derive(Debug, PartialEq, Eq)])))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ComparisonOp {
#[default]
GT,
GTE,
EQ,
LT,
LTE,
}
impl AsRef<[u8]> for ComparisonOp {
fn as_ref(&self) -> &[u8] {
match self {
Self::GT => b">",
Self::GTE => b">=",
Self::EQ => b"=",
Self::LT => b"<",
Self::LTE => b"<=",
}
}
}
impl Display for ComparisonOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::GT => write!(f, ">"),
Self::GTE => write!(f, ">="),
Self::EQ => write!(f, "="),
Self::LT => write!(f, "<"),
Self::LTE => write!(f, "<="),
}
}
}
#[cfg_attr(feature = "diff", derive(diff::Diff), diff(attr(#[derive(Debug, PartialEq, Eq)])))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum Sense {
#[default]
Minimize,
Maximize,
}
impl Sense {
#[inline]
#[must_use]
pub const fn is_minimisation(&self) -> bool {
matches!(self, Self::Minimize)
}
}
impl Display for Sense {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Minimize => write!(f, "Minimize"),
Self::Maximize => write!(f, "Maximize"),
}
}
}
#[cfg_attr(feature = "diff", derive(diff::Diff), diff(attr(#[derive(Debug, PartialEq, Eq)])))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SOSType {
#[default]
S1,
S2,
}
impl AsRef<[u8]> for SOSType {
fn as_ref(&self) -> &[u8] {
match self {
Self::S1 => b"S1",
Self::S2 => b"S2",
}
}
}
impl Display for SOSType {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::S1 => write!(f, "S1"),
Self::S2 => write!(f, "S2"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Coefficient {
pub name: NameId,
pub value: f64,
}
#[inline]
pub fn fmt_coefficient(name: &str, value: f64, f: &mut Formatter<'_>) -> FmtResult {
if (value - 1.0).abs() < crate::NUMERIC_EPSILON {
write!(f, "{name}")
} else if (value - (-1.0)).abs() < crate::NUMERIC_EPSILON {
write!(f, "-{name}")
} else {
write!(f, "{value} {name}")
}
}
#[derive(Debug, Clone)]
pub enum Constraint {
Standard {
name: NameId,
coefficients: Vec<Coefficient>,
operator: ComparisonOp,
rhs: f64,
byte_offset: Option<usize>,
},
SOS {
name: NameId,
sos_type: SOSType,
weights: Vec<Coefficient>,
byte_offset: Option<usize>,
},
}
impl PartialEq for Constraint {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Standard { name: n1, coefficients: c1, operator: o1, rhs: r1, .. },
Self::Standard { name: n2, coefficients: c2, operator: o2, rhs: r2, .. },
) => n1 == n2 && c1 == c2 && o1 == o2 && r1 == r2,
(Self::SOS { name: n1, sos_type: t1, weights: w1, .. }, Self::SOS { name: n2, sos_type: t2, weights: w2, .. }) => {
n1 == n2 && t1 == t2 && w1 == w2
}
_ => false,
}
}
}
impl Constraint {
#[must_use]
#[inline]
pub const fn name(&self) -> NameId {
match self {
Self::Standard { name, .. } | Self::SOS { name, .. } => *name,
}
}
#[must_use]
#[inline]
pub const fn byte_offset(&self) -> Option<usize> {
match self {
Self::Standard { byte_offset, .. } | Self::SOS { byte_offset, .. } => *byte_offset,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Objective {
pub name: NameId,
pub coefficients: Vec<Coefficient>,
pub byte_offset: Option<usize>,
}
#[cfg_attr(feature = "diff", derive(diff::Diff), diff(attr(#[derive(Debug, PartialEq)])))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)]
pub enum VariableType {
#[default]
Free,
General,
LowerBound(f64),
UpperBound(f64),
DoubleBound(f64, f64),
Binary,
Integer,
SemiContinuous,
SOS,
}
impl AsRef<[u8]> for VariableType {
fn as_ref(&self) -> &[u8] {
match self {
Self::Free => b"Free",
Self::General => b"General",
Self::LowerBound(_) => b"LowerBound",
Self::UpperBound(_) => b"UpperBound",
Self::DoubleBound(_, _) => b"DoubleBound",
Self::Binary => b"Binary",
Self::Integer => b"Integer",
Self::SemiContinuous => b"Semi-Continuous",
Self::SOS => b"SOS",
}
}
}
impl Display for VariableType {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Free => write!(f, "Free"),
Self::General => write!(f, "General"),
Self::LowerBound(_) => write!(f, "LowerBound"),
Self::UpperBound(_) => write!(f, "UpperBound"),
Self::DoubleBound(_, _) => write!(f, "DoubleBound"),
Self::Binary => write!(f, "Binary"),
Self::Integer => write!(f, "Integer"),
Self::SemiContinuous => write!(f, "Semi-Continuous"),
Self::SOS => write!(f, "SOS"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Variable {
pub name: NameId,
pub var_type: VariableType,
}
impl Variable {
#[must_use]
#[inline]
pub fn new(name: NameId) -> Self {
Self { name, var_type: VariableType::default() }
}
#[inline]
pub const fn set_var_type(&mut self, var_type: VariableType) {
self.var_type = var_type;
}
#[must_use]
#[inline]
pub const fn with_var_type(self, var_type: VariableType) -> Self {
Self { var_type, ..self }
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use super::*;
use crate::interner::NameInterner;
#[test]
fn test_comparison_op() {
assert_eq!(ComparisonOp::default(), ComparisonOp::GT);
let test_cases = [
(ComparisonOp::GT, b">".as_slice(), ">"),
(ComparisonOp::GTE, b">=".as_slice(), ">="),
(ComparisonOp::EQ, b"=".as_slice(), "="),
(ComparisonOp::LT, b"<".as_slice(), "<"),
(ComparisonOp::LTE, b"<=".as_slice(), "<="),
];
for (op, expected_bytes, expected_str) in test_cases {
assert_eq!(op.as_ref(), expected_bytes);
assert_eq!(format!("{op}"), expected_str);
assert_eq!(op.clone(), op);
}
}
#[test]
fn test_sense() {
assert_eq!(Sense::default(), Sense::Minimize);
assert!(Sense::Minimize.is_minimisation());
assert!(!Sense::Maximize.is_minimisation());
assert_eq!(format!("{}", Sense::Minimize), "Minimize");
assert_eq!(format!("{}", Sense::Maximize), "Maximize");
assert_ne!(Sense::Minimize, Sense::Maximize);
}
#[test]
fn test_sos_type() {
let test_cases = [(SOSType::S1, b"S1".as_slice(), "S1"), (SOSType::S2, b"S2".as_slice(), "S2")];
for (sos, expected_bytes, expected_str) in test_cases {
assert_eq!(sos.as_ref(), expected_bytes);
assert_eq!(format!("{sos}"), expected_str);
assert_eq!(sos.clone(), sos);
}
assert_ne!(SOSType::S1, SOSType::S2);
}
#[test]
fn test_coefficient() {
let mut interner = NameInterner::new();
let x1 = interner.intern("x1");
let x = interner.intern("x");
let coeff = Coefficient { name: x1, value: 2.5 };
assert_eq!(interner.resolve(coeff.name), "x1");
assert_eq!(coeff.value, 2.5);
assert_eq!(coeff.clone(), coeff);
let mut buf = String::new();
write!(buf, "{}", FmtCoeff { name: "x", value: 1.0 }).unwrap();
assert_eq!(buf, "x");
buf.clear();
write!(buf, "{}", FmtCoeff { name: "x", value: -1.0 }).unwrap();
assert_eq!(buf, "-x");
buf.clear();
write!(buf, "{}", FmtCoeff { name: "x", value: 2.5 }).unwrap();
assert_eq!(buf, "2.5 x");
buf.clear();
write!(buf, "{}", FmtCoeff { name: "x", value: 0.0 }).unwrap();
assert_eq!(buf, "0 x");
let _ = x;
}
struct FmtCoeff {
name: &'static str,
value: f64,
}
impl Display for FmtCoeff {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
fmt_coefficient(self.name, self.value, f)
}
}
#[test]
fn test_constraint() {
let mut interner = NameInterner::new();
let c1 = interner.intern("c1");
let x1 = interner.intern("x1");
let x2 = interner.intern("x2");
let sos1 = interner.intern("sos1");
let std_constraint = Constraint::Standard {
name: c1,
coefficients: vec![Coefficient { name: x1, value: 2.0 }, Coefficient { name: x2, value: -1.0 }],
operator: ComparisonOp::LTE,
rhs: 10.0,
byte_offset: None,
};
assert_eq!(std_constraint.name(), c1);
assert_eq!(interner.resolve(std_constraint.name()), "c1");
let sos_constraint =
Constraint::SOS { name: sos1, sos_type: SOSType::S1, weights: vec![Coefficient { name: x1, value: 1.0 }], byte_offset: None };
assert_eq!(sos_constraint.name(), sos1);
assert_eq!(interner.resolve(sos_constraint.name()), "sos1");
let e = interner.intern("e");
let empty = Constraint::Standard { name: e, coefficients: vec![], operator: ComparisonOp::EQ, rhs: 0.0, byte_offset: None };
if let Constraint::Standard { coefficients, .. } = empty {
assert!(coefficients.is_empty());
}
}
#[test]
fn test_objective() {
let mut interner = NameInterner::new();
let profit = interner.intern("profit");
let x1 = interner.intern("x1");
let obj = Objective { name: profit, coefficients: vec![Coefficient { name: x1, value: 5.0 }], byte_offset: None };
assert_eq!(interner.resolve(obj.name), "profit");
assert_eq!(obj.coefficients.len(), 1);
let dynamic = interner.intern("dynamic");
let obj_empty = Objective { name: dynamic, coefficients: vec![], byte_offset: None };
assert_eq!(interner.resolve(obj_empty.name), "dynamic");
assert!(obj_empty.coefficients.is_empty());
}
#[test]
fn test_variable_type() {
assert_eq!(VariableType::default(), VariableType::Free);
let test_cases = [
(VariableType::Free, b"Free".as_slice(), "Free"),
(VariableType::General, b"General".as_slice(), "General"),
(VariableType::Binary, b"Binary".as_slice(), "Binary"),
(VariableType::Integer, b"Integer".as_slice(), "Integer"),
(VariableType::SemiContinuous, b"Semi-Continuous".as_slice(), "Semi-Continuous"),
(VariableType::SOS, b"SOS".as_slice(), "SOS"),
(VariableType::LowerBound(5.0), b"LowerBound".as_slice(), "LowerBound"),
(VariableType::UpperBound(10.0), b"UpperBound".as_slice(), "UpperBound"),
(VariableType::DoubleBound(0.0, 100.0), b"DoubleBound".as_slice(), "DoubleBound"),
];
for (vt, expected_bytes, expected_str) in test_cases {
assert_eq!(vt.as_ref(), expected_bytes);
assert_eq!(format!("{vt}"), expected_str);
assert_eq!(vt.clone(), vt);
}
if let VariableType::DoubleBound(l, u) = VariableType::DoubleBound(0.0, 100.0) {
assert_eq!((l, u), (0.0, 100.0));
}
}
#[test]
fn test_variable() {
let mut interner = NameInterner::new();
let x1 = interner.intern("x1");
let x = interner.intern("x");
let y = interner.intern("y");
let var = Variable::new(x1);
assert_eq!(interner.resolve(var.name), "x1");
assert_eq!(var.var_type, VariableType::Free);
let var_binary = Variable::new(x).with_var_type(VariableType::Binary);
assert_eq!(var_binary.var_type, VariableType::Binary);
let mut var_mut = Variable::new(y);
var_mut.set_var_type(VariableType::Integer);
assert_eq!(var_mut.var_type, VariableType::Integer);
assert_eq!(Variable::new(x).with_var_type(VariableType::Binary), Variable::new(x).with_var_type(VariableType::Binary));
assert_ne!(Variable::new(x).with_var_type(VariableType::Binary), Variable::new(y).with_var_type(VariableType::Binary));
}
}