mod approx;
mod arbitrary;
mod evaluate;
mod logical_memory;
mod metadata_store;
mod parse;
mod reduce_binary_power;
pub(crate) mod stage;
pub use metadata_store::ConstraintMetadataStore;
use crate::logical_memory::LogicalMemoryProfile;
use crate::{Function, SampleID, VariableID};
pub use arbitrary::*;
use derive_more::{Deref, From};
use fnv::{FnvHashMap, FnvHashSet};
pub use stage::{
Created, CreatedData, Evaluated, EvaluatedData, RemovedReason, SampledData, Stage,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Equality {
EqualToZero,
LessThanOrEqualToZero,
}
#[derive(
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
From,
Deref,
serde::Serialize,
serde::Deserialize,
)]
#[serde(transparent)]
pub struct ConstraintID(u64);
impl std::fmt::Debug for ConstraintID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ConstraintID({})", self.0)
}
}
impl std::fmt::Display for ConstraintID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl ConstraintID {
pub fn into_inner(self) -> u64 {
self.0
}
}
impl From<ConstraintID> for u64 {
fn from(id: ConstraintID) -> Self {
id.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Provenance {
IndicatorConstraint(crate::IndicatorConstraintID),
OneHotConstraint(crate::OneHotConstraintID),
Sos1Constraint(crate::Sos1ConstraintID),
}
#[derive(Debug, Clone, PartialEq, Default, LogicalMemoryProfile)]
pub struct ConstraintMetadata {
pub name: Option<String>,
pub subscripts: Vec<i64>,
pub parameters: FnvHashMap<String, String>,
pub description: Option<String>,
pub provenance: Vec<Provenance>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Constraint<S: Stage<Self> = Created> {
pub equality: Equality,
pub stage: S::Data,
}
impl Constraint<Created> {
pub fn function(&self) -> &Function {
&self.stage.function
}
pub fn function_mut(&mut self) -> &mut Function {
&mut self.stage.function
}
pub fn equal_to_zero(function: Function) -> Self {
Self {
equality: Equality::EqualToZero,
stage: CreatedData { function },
}
}
pub fn less_than_or_equal_to_zero(function: Function) -> Self {
Self {
equality: Equality::LessThanOrEqualToZero,
stage: CreatedData { function },
}
}
}
impl std::fmt::Display for Constraint<Created> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let equality_symbol = match self.equality {
Equality::EqualToZero => "==",
Equality::LessThanOrEqualToZero => "<=",
};
write!(
f,
"Constraint({} {} 0)",
self.stage.function, equality_symbol
)
}
}
pub type EvaluatedConstraint = Constraint<Evaluated>;
impl EvaluatedConstraint {
pub fn is_feasible_with_tolerance(&self, atol: crate::ATol) -> bool {
match self.equality {
Equality::EqualToZero => self.stage.evaluated_value.abs() < *atol,
Equality::LessThanOrEqualToZero => self.stage.evaluated_value < *atol,
}
}
pub fn violation(&self) -> f64 {
match self.equality {
Equality::EqualToZero => self.stage.evaluated_value.abs(),
Equality::LessThanOrEqualToZero => self.stage.evaluated_value.max(0.0),
}
}
}
pub(crate) fn evaluated_constraint_to_v1(
id: ConstraintID,
c: EvaluatedConstraint,
metadata: ConstraintMetadata,
) -> crate::v1::EvaluatedConstraint {
crate::v1::EvaluatedConstraint {
id: id.into_inner(),
equality: c.equality.into(),
evaluated_value: c.stage.evaluated_value,
used_decision_variable_ids: c
.stage
.used_decision_variable_ids
.into_iter()
.map(|id| id.into_inner())
.collect(),
subscripts: metadata.subscripts,
parameters: metadata.parameters.into_iter().collect(),
name: metadata.name,
description: metadata.description,
dual_variable: c.stage.dual_variable,
removed_reason: None,
removed_reason_parameters: Default::default(),
}
}
pub type SampledConstraint = Constraint<stage::Sampled>;
impl SampledConstraint {
pub fn is_feasible(&self, sample_id: SampleID, atol: crate::ATol) -> Option<bool> {
let evaluated_value = *self.stage.evaluated_values.get(sample_id)?;
Some(match self.equality {
Equality::EqualToZero => evaluated_value.abs() < *atol,
Equality::LessThanOrEqualToZero => evaluated_value < *atol,
})
}
pub fn feasible_ids(&self, atol: crate::ATol) -> FnvHashSet<SampleID> {
self.stage
.evaluated_values
.iter()
.filter_map(|(sample_id, evaluated_value)| {
let feasible = match self.equality {
Equality::EqualToZero => evaluated_value.abs() < *atol,
Equality::LessThanOrEqualToZero => *evaluated_value < *atol,
};
if feasible {
Some(*sample_id)
} else {
None
}
})
.collect()
}
pub fn infeasible_ids(&self, atol: crate::ATol) -> FnvHashSet<SampleID> {
self.stage
.evaluated_values
.iter()
.filter_map(|(sample_id, evaluated_value)| {
let feasible = match self.equality {
Equality::EqualToZero => evaluated_value.abs() < *atol,
Equality::LessThanOrEqualToZero => *evaluated_value < *atol,
};
if !feasible {
Some(*sample_id)
} else {
None
}
})
.collect()
}
}
pub(crate) fn sampled_constraint_to_v1(
id: ConstraintID,
c: SampledConstraint,
metadata: ConstraintMetadata,
) -> crate::v1::SampledConstraint {
let evaluated_values: crate::v1::SampledValues = c.stage.evaluated_values.into();
let feasible = c
.stage
.feasible
.into_iter()
.map(|(id, value)| (id.into_inner(), value))
.collect();
crate::v1::SampledConstraint {
id: id.into_inner(),
equality: c.equality.into(),
name: metadata.name,
subscripts: metadata.subscripts,
parameters: metadata.parameters.into_iter().collect(),
description: metadata.description,
removed_reason: None,
removed_reason_parameters: Default::default(),
evaluated_values: Some(evaluated_values),
used_decision_variable_ids: c
.stage
.used_decision_variable_ids
.into_iter()
.map(|id| id.into_inner())
.collect(),
feasible,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Coefficient, Evaluate};
#[test]
fn test_violation_equality_positive() {
let constraint =
Constraint::equal_to_zero(Function::Constant(Coefficient::try_from(2.5).unwrap()));
let state = crate::v1::State::default();
let evaluated = constraint.evaluate(&state, crate::ATol::default()).unwrap();
assert_eq!(evaluated.violation(), 2.5);
}
#[test]
fn test_violation_equality_negative() {
let constraint =
Constraint::equal_to_zero(Function::Constant(Coefficient::try_from(-3.0).unwrap()));
let state = crate::v1::State::default();
let evaluated = constraint.evaluate(&state, crate::ATol::default()).unwrap();
assert_eq!(evaluated.violation(), 3.0);
}
#[test]
fn test_violation_equality_near_zero() {
let constraint =
Constraint::equal_to_zero(Function::Constant(Coefficient::try_from(0.0001).unwrap()));
let state = crate::v1::State::default();
let evaluated = constraint.evaluate(&state, crate::ATol::default()).unwrap();
assert_eq!(evaluated.violation(), 0.0001);
}
#[test]
fn test_violation_inequality_violated() {
let constraint = Constraint::less_than_or_equal_to_zero(Function::Constant(
Coefficient::try_from(1.5).unwrap(),
));
let state = crate::v1::State::default();
let evaluated = constraint.evaluate(&state, crate::ATol::default()).unwrap();
assert_eq!(evaluated.violation(), 1.5);
}
#[test]
fn test_violation_inequality_satisfied() {
let constraint = Constraint::less_than_or_equal_to_zero(Function::Constant(
Coefficient::try_from(-1.0).unwrap(),
));
let state = crate::v1::State::default();
let evaluated = constraint.evaluate(&state, crate::ATol::default()).unwrap();
assert_eq!(evaluated.violation(), 0.0);
}
#[test]
fn test_violation_inequality_near_boundary() {
let constraint = Constraint::less_than_or_equal_to_zero(Function::Constant(
Coefficient::try_from(0.0001).unwrap(),
));
let state = crate::v1::State::default();
let evaluated = constraint.evaluate(&state, crate::ATol::default()).unwrap();
assert_eq!(evaluated.violation(), 0.0001);
}
}