use crate::error::Error;
use crate::num::{IntoRational, Rational};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Sense {
Maximize,
Minimize,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Relation {
#[cfg_attr(feature = "serde", serde(rename = "<="))]
Le,
#[cfg_attr(feature = "serde", serde(rename = ">="))]
Ge,
#[cfg_attr(feature = "serde", serde(rename = "="))]
Eq,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Constraint {
#[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::vec"))]
pub coeffs: Vec<Rational>,
pub relation: Relation,
#[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::one"))]
pub rhs: Rational,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Problem {
pub sense: Sense,
#[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::vec"))]
pub objective: Vec<Rational>,
pub constraints: Vec<Constraint>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub var_names: Vec<String>,
}
impl Problem {
pub fn maximize<T: IntoRational>(objective: impl IntoIterator<Item = T>) -> Self {
Self::new(Sense::Maximize, objective)
}
pub fn minimize<T: IntoRational>(objective: impl IntoIterator<Item = T>) -> Self {
Self::new(Sense::Minimize, objective)
}
fn new<T: IntoRational>(sense: Sense, objective: impl IntoIterator<Item = T>) -> Self {
let objective: Vec<Rational> = objective
.into_iter()
.map(IntoRational::into_rational)
.collect();
Problem {
sense,
objective,
constraints: Vec::new(),
var_names: Vec::new(),
}
}
pub fn subject_to<T: IntoRational>(
mut self,
coeffs: impl IntoIterator<Item = T>,
relation: Relation,
rhs: impl IntoRational,
) -> Self {
self.constraints.push(Constraint {
coeffs: coeffs
.into_iter()
.map(IntoRational::into_rational)
.collect(),
relation,
rhs: rhs.into_rational(),
});
self
}
pub fn with_names<S: Into<String>>(mut self, names: impl IntoIterator<Item = S>) -> Self {
self.var_names = names.into_iter().map(Into::into).collect();
self
}
pub fn num_vars(&self) -> usize {
self.objective.len()
}
pub fn var_name(&self, j: usize) -> String {
self.var_names
.get(j)
.cloned()
.unwrap_or_else(|| format!("x{}", j + 1))
}
pub(crate) fn validate(&self) -> Result<(), Error> {
let n = self.num_vars();
if n == 0 {
return Err(Error::InvalidProblem(
"the objective has no variables".into(),
));
}
if !self.var_names.is_empty() && self.var_names.len() != n {
return Err(Error::InvalidProblem(format!(
"{} variable names for {n} variables",
self.var_names.len()
)));
}
for (i, c) in self.constraints.iter().enumerate() {
if c.coeffs.len() != n {
return Err(Error::InvalidProblem(format!(
"constraint {} has {} coefficients, expected {n}",
i + 1,
c.coeffs.len()
)));
}
}
Ok(())
}
}