use crate::Result;
use crate::{
constraint::{
ConstraintID, ConstraintMetadata, ConstraintMetadataStore, EvaluatedConstraint,
RemovedReason, SampledConstraint,
},
v1, ATol, Constraint, Evaluate, SampleID, VariableIDSet,
};
use std::collections::BTreeMap;
pub trait IDType:
Clone
+ Copy
+ Ord
+ std::hash::Hash
+ std::fmt::Debug
+ From<u64>
+ Into<u64>
+ crate::logical_memory::LogicalMemoryProfile
{
}
impl<T> IDType for T where
T: Clone
+ Copy
+ Ord
+ std::hash::Hash
+ std::fmt::Debug
+ From<u64>
+ Into<u64>
+ crate::logical_memory::LogicalMemoryProfile
{
}
pub trait ConstraintType {
type ID: IDType;
type Created: Evaluate<Output = Self::Evaluated, SampledOutput = Self::Sampled>
+ Clone
+ std::fmt::Debug
+ PartialEq;
type Evaluated: EvaluatedConstraintBehavior<ID = Self::ID>;
type Sampled: SampledConstraintBehavior<ID = Self::ID, Evaluated = Self::Evaluated>;
}
pub trait EvaluatedConstraintBehavior {
type ID;
fn is_feasible(&self) -> bool;
}
pub trait SampledConstraintBehavior {
type ID;
type Evaluated;
fn is_feasible_for(&self, sample_id: SampleID) -> Option<bool>;
fn get(&self, sample_id: SampleID) -> Option<Self::Evaluated>;
}
impl EvaluatedConstraintBehavior for EvaluatedConstraint {
type ID = ConstraintID;
fn is_feasible(&self) -> bool {
self.stage.feasible
}
}
impl SampledConstraintBehavior for SampledConstraint {
type ID = ConstraintID;
type Evaluated = EvaluatedConstraint;
fn is_feasible_for(&self, sample_id: SampleID) -> Option<bool> {
self.stage.feasible.get(&sample_id).copied()
}
fn get(&self, sample_id: SampleID) -> Option<Self::Evaluated> {
use crate::constraint::EvaluatedData;
let evaluated_value = *self.stage.evaluated_values.get(sample_id)?;
let dual_variable = self
.stage
.dual_variables
.as_ref()
.and_then(|duals| duals.get(sample_id))
.copied();
let feasible = *self.stage.feasible.get(&sample_id)?;
Some(crate::Constraint {
equality: self.equality,
stage: EvaluatedData {
evaluated_value,
dual_variable,
feasible,
used_decision_variable_ids: self.stage.used_decision_variable_ids.clone(),
},
})
}
}
impl ConstraintType for Constraint {
type ID = ConstraintID;
type Created = Constraint;
type Evaluated = EvaluatedConstraint;
type Sampled = SampledConstraint;
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConstraintCollection<T: ConstraintType> {
active: BTreeMap<T::ID, T::Created>,
removed: BTreeMap<T::ID, (T::Created, RemovedReason)>,
metadata: ConstraintMetadataStore<T::ID>,
}
impl<T: ConstraintType> Default for ConstraintCollection<T> {
fn default() -> Self {
Self {
active: BTreeMap::new(),
removed: BTreeMap::new(),
metadata: ConstraintMetadataStore::default(),
}
}
}
impl<T: ConstraintType> ConstraintCollection<T> {
pub fn new(
active: BTreeMap<T::ID, T::Created>,
removed: BTreeMap<T::ID, (T::Created, RemovedReason)>,
) -> Self {
Self {
active,
removed,
metadata: ConstraintMetadataStore::default(),
}
}
pub fn with_metadata(
active: BTreeMap<T::ID, T::Created>,
removed: BTreeMap<T::ID, (T::Created, RemovedReason)>,
metadata: ConstraintMetadataStore<T::ID>,
) -> Self {
Self {
active,
removed,
metadata,
}
}
pub fn metadata(&self) -> &ConstraintMetadataStore<T::ID> {
&self.metadata
}
pub fn metadata_mut(&mut self) -> &mut ConstraintMetadataStore<T::ID> {
&mut self.metadata
}
pub fn active(&self) -> &BTreeMap<T::ID, T::Created> {
&self.active
}
pub fn removed(&self) -> &BTreeMap<T::ID, (T::Created, RemovedReason)> {
&self.removed
}
pub(crate) fn active_mut(&mut self) -> &mut BTreeMap<T::ID, T::Created> {
&mut self.active
}
pub(crate) fn removed_mut(&mut self) -> &mut BTreeMap<T::ID, (T::Created, RemovedReason)> {
&mut self.removed
}
pub(crate) fn insert_with(
&mut self,
id: T::ID,
constraint: T::Created,
metadata: ConstraintMetadata,
) {
self.active.insert(id, constraint);
self.metadata.insert(id, metadata);
}
pub fn unused_id(&self) -> T::ID {
let max_active = self.active.keys().last().copied().map(Into::into);
let max_removed = self.removed.keys().last().copied().map(Into::into);
let next = match (max_active, max_removed) {
(None, None) => 0u64,
(Some(a), None) => a.checked_add(1).expect("constraint ID space exhausted"),
(None, Some(r)) => r.checked_add(1).expect("constraint ID space exhausted"),
(Some(a), Some(r)) => a
.max(r)
.checked_add(1)
.expect("constraint ID space exhausted"),
};
T::ID::from(next)
}
#[allow(clippy::type_complexity)]
pub fn into_parts(
self,
) -> (
BTreeMap<T::ID, T::Created>,
BTreeMap<T::ID, (T::Created, RemovedReason)>,
ConstraintMetadataStore<T::ID>,
) {
(self.active, self.removed, self.metadata)
}
pub fn relax(&mut self, id: T::ID, removed_reason: RemovedReason) -> crate::Result<()> {
let c = self
.active
.remove(&id)
.ok_or_else(|| crate::error!("Constraint with ID {:?} not found", id))?;
self.removed.insert(id, (c, removed_reason));
Ok(())
}
pub fn restore(&mut self, id: T::ID) -> crate::Result<()> {
let (constraint, _reason) = self
.removed
.remove(&id)
.ok_or_else(|| crate::error!("Removed constraint with ID {:?} not found", id))?;
self.active.insert(id, constraint);
Ok(())
}
pub fn required_ids(&self) -> VariableIDSet {
let mut ids = VariableIDSet::default();
for constraint in self.active.values() {
ids.extend(constraint.required_ids());
}
ids
}
}
impl<T: ConstraintType> Evaluate for ConstraintCollection<T> {
type Output = EvaluatedCollection<T>;
type SampledOutput = SampledCollection<T>;
fn evaluate(&self, state: &v1::State, atol: ATol) -> Result<Self::Output> {
let mut results = BTreeMap::new();
let mut removed_reasons = BTreeMap::new();
for (id, constraint) in &self.active {
let evaluated = constraint.evaluate(state, atol).inspect_err(|e| {
tracing::error!(?id, error = %e, "failed to evaluate active constraint");
})?;
results.insert(*id, evaluated);
}
for (id, (constraint, reason)) in &self.removed {
let evaluated = constraint.evaluate(state, atol).inspect_err(|e| {
tracing::error!(?id, error = %e, "failed to evaluate removed constraint");
})?;
results.insert(*id, evaluated);
removed_reasons.insert(*id, reason.clone());
}
Ok(EvaluatedCollection::with_metadata(
results,
removed_reasons,
self.metadata.clone(),
))
}
fn evaluate_samples(
&self,
samples: &crate::Sampled<v1::State>,
atol: ATol,
) -> Result<Self::SampledOutput> {
let mut results = BTreeMap::new();
let mut removed_reasons = BTreeMap::new();
for (id, constraint) in &self.active {
let evaluated = constraint.evaluate_samples(samples, atol).inspect_err(|e| {
tracing::error!(?id, error = %e, "failed to evaluate_samples active constraint");
})?;
results.insert(*id, evaluated);
}
for (id, (constraint, reason)) in &self.removed {
let evaluated = constraint.evaluate_samples(samples, atol).inspect_err(|e| {
tracing::error!(?id, error = %e, "failed to evaluate_samples removed constraint");
})?;
results.insert(*id, evaluated);
removed_reasons.insert(*id, reason.clone());
}
Ok(SampledCollection::with_metadata(
results,
removed_reasons,
self.metadata.clone(),
))
}
fn partial_evaluate(&mut self, state: &v1::State, atol: ATol) -> Result<()> {
for (id, constraint) in self.active.iter_mut() {
constraint.partial_evaluate(state, atol).inspect_err(|e| {
tracing::error!(?id, error = %e, "failed to partial_evaluate constraint");
})?;
}
Ok(())
}
fn required_ids(&self) -> VariableIDSet {
ConstraintCollection::required_ids(self)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EvaluatedCollection<T: ConstraintType> {
constraints: BTreeMap<T::ID, T::Evaluated>,
removed_reasons: BTreeMap<T::ID, RemovedReason>,
metadata: ConstraintMetadataStore<T::ID>,
}
impl<T: ConstraintType> std::ops::Deref for EvaluatedCollection<T> {
type Target = BTreeMap<T::ID, T::Evaluated>;
fn deref(&self) -> &Self::Target {
&self.constraints
}
}
impl<T: ConstraintType> std::ops::DerefMut for EvaluatedCollection<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.constraints
}
}
impl<T: ConstraintType> Default for EvaluatedCollection<T> {
fn default() -> Self {
Self {
constraints: BTreeMap::new(),
removed_reasons: BTreeMap::new(),
metadata: ConstraintMetadataStore::default(),
}
}
}
impl<T: ConstraintType> EvaluatedCollection<T> {
pub fn new(
constraints: BTreeMap<T::ID, T::Evaluated>,
removed_reasons: BTreeMap<T::ID, RemovedReason>,
) -> Self {
Self {
constraints,
removed_reasons,
metadata: ConstraintMetadataStore::default(),
}
}
pub fn with_metadata(
constraints: BTreeMap<T::ID, T::Evaluated>,
removed_reasons: BTreeMap<T::ID, RemovedReason>,
metadata: ConstraintMetadataStore<T::ID>,
) -> Self {
Self {
constraints,
removed_reasons,
metadata,
}
}
pub fn inner(&self) -> &BTreeMap<T::ID, T::Evaluated> {
&self.constraints
}
pub fn into_inner(self) -> BTreeMap<T::ID, T::Evaluated> {
self.constraints
}
pub fn removed_reasons(&self) -> &BTreeMap<T::ID, RemovedReason> {
&self.removed_reasons
}
pub fn metadata(&self) -> &ConstraintMetadataStore<T::ID> {
&self.metadata
}
pub fn metadata_mut(&mut self) -> &mut ConstraintMetadataStore<T::ID> {
&mut self.metadata
}
#[allow(clippy::type_complexity)]
pub fn into_parts(
self,
) -> (
BTreeMap<T::ID, T::Evaluated>,
BTreeMap<T::ID, RemovedReason>,
ConstraintMetadataStore<T::ID>,
) {
(self.constraints, self.removed_reasons, self.metadata)
}
pub fn is_removed(&self, id: &T::ID) -> bool {
self.removed_reasons.contains_key(id)
}
pub fn is_empty(&self) -> bool {
self.constraints.is_empty()
}
pub fn is_feasible(&self) -> bool {
self.constraints.values().all(|c| c.is_feasible())
}
pub fn is_feasible_relaxed(&self) -> bool {
self.constraints
.iter()
.filter(|(id, _)| !self.removed_reasons.contains_key(id))
.all(|(_, c)| c.is_feasible())
}
}
#[derive(Debug, Clone)]
pub struct SampledCollection<T: ConstraintType> {
constraints: BTreeMap<T::ID, T::Sampled>,
removed_reasons: BTreeMap<T::ID, RemovedReason>,
metadata: ConstraintMetadataStore<T::ID>,
}
impl<T: ConstraintType> std::ops::Deref for SampledCollection<T> {
type Target = BTreeMap<T::ID, T::Sampled>;
fn deref(&self) -> &Self::Target {
&self.constraints
}
}
impl<T: ConstraintType> Default for SampledCollection<T> {
fn default() -> Self {
Self {
constraints: BTreeMap::new(),
removed_reasons: BTreeMap::new(),
metadata: ConstraintMetadataStore::default(),
}
}
}
impl<T: ConstraintType> SampledCollection<T> {
pub fn new(
constraints: BTreeMap<T::ID, T::Sampled>,
removed_reasons: BTreeMap<T::ID, RemovedReason>,
) -> Self {
Self {
constraints,
removed_reasons,
metadata: ConstraintMetadataStore::default(),
}
}
pub fn with_metadata(
constraints: BTreeMap<T::ID, T::Sampled>,
removed_reasons: BTreeMap<T::ID, RemovedReason>,
metadata: ConstraintMetadataStore<T::ID>,
) -> Self {
Self {
constraints,
removed_reasons,
metadata,
}
}
pub fn inner(&self) -> &BTreeMap<T::ID, T::Sampled> {
&self.constraints
}
pub fn into_inner(self) -> BTreeMap<T::ID, T::Sampled> {
self.constraints
}
pub fn removed_reasons(&self) -> &BTreeMap<T::ID, RemovedReason> {
&self.removed_reasons
}
pub fn metadata(&self) -> &ConstraintMetadataStore<T::ID> {
&self.metadata
}
pub fn metadata_mut(&mut self) -> &mut ConstraintMetadataStore<T::ID> {
&mut self.metadata
}
#[allow(clippy::type_complexity)]
pub fn into_parts(
self,
) -> (
BTreeMap<T::ID, T::Sampled>,
BTreeMap<T::ID, RemovedReason>,
ConstraintMetadataStore<T::ID>,
) {
(self.constraints, self.removed_reasons, self.metadata)
}
pub fn is_removed(&self, id: &T::ID) -> bool {
self.removed_reasons.contains_key(id)
}
pub fn is_empty(&self) -> bool {
self.constraints.is_empty()
}
pub fn is_feasible_for(&self, sample_id: SampleID) -> bool {
self.constraints
.values()
.all(|c| c.is_feasible_for(sample_id).unwrap_or(false))
}
pub fn is_feasible_relaxed_for(&self, sample_id: SampleID) -> bool {
self.constraints
.iter()
.filter(|(id, _)| !self.removed_reasons.contains_key(id))
.all(|(_, c)| c.is_feasible_for(sample_id).unwrap_or(false))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{coeff, constraint::ConstraintID, linear, Function};
#[test]
fn constraint_type_aliases() {
let c = Constraint::equal_to_zero(Function::Zero);
let _: <Constraint as ConstraintType>::Created = c;
}
#[test]
fn empty_collection() {
let collection = ConstraintCollection::<Constraint>::default();
assert!(collection.active().is_empty());
assert!(collection.removed().is_empty());
}
#[test]
fn evaluate_collection() {
let mut active = BTreeMap::new();
active.insert(
ConstraintID::from(1),
Constraint::less_than_or_equal_to_zero(Function::from(linear!(1) + coeff!(-1.0))),
);
active.insert(
ConstraintID::from(2),
Constraint::equal_to_zero(Function::from(linear!(1) + coeff!(-2.0))),
);
let collection = ConstraintCollection::<Constraint>::new(active, BTreeMap::new());
let state = v1::State {
entries: [(1, 1.5)].into_iter().collect(),
};
let results = collection.evaluate(&state, ATol::default()).unwrap();
assert_eq!(results.len(), 2);
assert!(!results[&ConstraintID::from(1)].stage.feasible);
assert!(!results[&ConstraintID::from(2)].stage.feasible);
assert!(results.removed_reasons().is_empty());
}
#[test]
fn collection_accessors() {
let mut active = BTreeMap::new();
active.insert(
ConstraintID::from(1),
Constraint::equal_to_zero(Function::Zero),
);
let collection = ConstraintCollection::<Constraint>::new(active, BTreeMap::new());
assert_eq!(collection.active().len(), 1);
assert_eq!(collection.removed().len(), 0);
}
}