mod analysis;
mod approx;
pub(crate) mod arbitrary;
mod builder;
mod clip_bounds;
mod convert;
mod decision_variable;
mod evaluate;
mod indicator;
mod log_encode;
mod logical_memory;
mod named_function;
mod new;
mod one_hot;
mod parametric_builder;
mod parse;
mod pass;
mod penalty;
mod qubo;
mod reduce_binary_power;
mod serialize;
mod setter;
mod slack;
mod sos1;
mod stats;
mod substitute;
pub use analysis::*;
pub use arbitrary::InstanceParameters;
pub use builder::*;
pub use parametric_builder::*;
pub use stats::*;
use crate::{
constraint::{ConstraintMetadataStore, RemovedReason},
constraint_type::ConstraintCollection,
decision_variable::VariableMetadataStore,
indicator_constraint::IndicatorConstraint,
named_function::NamedFunctionID,
one_hot_constraint::OneHotConstraint,
sos1_constraint::Sos1Constraint,
v1, AcyclicAssignments, Constraint, ConstraintID, DecisionVariable, Evaluate, Function,
NamedFunction, VariableID, VariableIDSet,
};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AdditionalCapability {
Indicator,
OneHot,
Sos1,
}
pub type Capabilities = std::collections::BTreeSet<AdditionalCapability>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Sense {
#[default]
Minimize,
Maximize,
}
#[derive(
Debug,
Clone,
PartialEq,
getset::Getters,
getset::CopyGetters,
Default,
crate::logical_memory::LogicalMemoryProfile,
)]
pub struct Instance {
#[getset(get_copy = "pub")]
sense: Sense,
#[getset(get = "pub")]
objective: Function,
#[getset(get = "pub")]
decision_variables: BTreeMap<VariableID, DecisionVariable>,
variable_metadata: VariableMetadataStore,
constraint_collection: ConstraintCollection<Constraint>,
indicator_constraint_collection: ConstraintCollection<IndicatorConstraint>,
one_hot_constraint_collection: ConstraintCollection<OneHotConstraint>,
sos1_constraint_collection: ConstraintCollection<Sos1Constraint>,
#[getset(get = "pub")]
decision_variable_dependency: AcyclicAssignments,
#[getset(get = "pub")]
named_functions: BTreeMap<NamedFunctionID, NamedFunction>,
named_function_metadata: crate::named_function::NamedFunctionMetadataStore,
pub parameters: Option<v1::Parameters>,
pub description: Option<v1::instance::Description>,
}
impl Instance {
pub fn variable_metadata(&self) -> &VariableMetadataStore {
&self.variable_metadata
}
pub fn variable_metadata_mut(&mut self) -> &mut VariableMetadataStore {
&mut self.variable_metadata
}
pub fn named_function_metadata(&self) -> &crate::named_function::NamedFunctionMetadataStore {
&self.named_function_metadata
}
pub fn named_function_metadata_mut(
&mut self,
) -> &mut crate::named_function::NamedFunctionMetadataStore {
&mut self.named_function_metadata
}
pub fn constraints(&self) -> &BTreeMap<ConstraintID, Constraint> {
self.constraint_collection.active()
}
pub fn removed_constraints(&self) -> &BTreeMap<ConstraintID, (Constraint, RemovedReason)> {
self.constraint_collection.removed()
}
pub fn constraint_collection(&self) -> &ConstraintCollection<Constraint> {
&self.constraint_collection
}
pub fn constraint_metadata(&self) -> &ConstraintMetadataStore<ConstraintID> {
self.constraint_collection.metadata()
}
pub fn constraint_metadata_mut(&mut self) -> &mut ConstraintMetadataStore<ConstraintID> {
self.constraint_collection.metadata_mut()
}
pub fn indicator_constraints(
&self,
) -> &BTreeMap<crate::IndicatorConstraintID, IndicatorConstraint> {
self.indicator_constraint_collection.active()
}
pub fn removed_indicator_constraints(
&self,
) -> &BTreeMap<crate::IndicatorConstraintID, (IndicatorConstraint, RemovedReason)> {
self.indicator_constraint_collection.removed()
}
pub fn indicator_constraint_collection(&self) -> &ConstraintCollection<IndicatorConstraint> {
&self.indicator_constraint_collection
}
pub fn indicator_constraint_metadata(
&self,
) -> &ConstraintMetadataStore<crate::IndicatorConstraintID> {
self.indicator_constraint_collection.metadata()
}
pub fn indicator_constraint_metadata_mut(
&mut self,
) -> &mut ConstraintMetadataStore<crate::IndicatorConstraintID> {
self.indicator_constraint_collection.metadata_mut()
}
pub fn one_hot_constraints(&self) -> &BTreeMap<crate::OneHotConstraintID, OneHotConstraint> {
self.one_hot_constraint_collection.active()
}
pub fn removed_one_hot_constraints(
&self,
) -> &BTreeMap<crate::OneHotConstraintID, (OneHotConstraint, RemovedReason)> {
self.one_hot_constraint_collection.removed()
}
pub fn one_hot_constraint_collection(&self) -> &ConstraintCollection<OneHotConstraint> {
&self.one_hot_constraint_collection
}
pub fn one_hot_constraint_metadata(
&self,
) -> &ConstraintMetadataStore<crate::OneHotConstraintID> {
self.one_hot_constraint_collection.metadata()
}
pub fn one_hot_constraint_metadata_mut(
&mut self,
) -> &mut ConstraintMetadataStore<crate::OneHotConstraintID> {
self.one_hot_constraint_collection.metadata_mut()
}
pub fn sos1_constraints(&self) -> &BTreeMap<crate::Sos1ConstraintID, Sos1Constraint> {
self.sos1_constraint_collection.active()
}
pub fn removed_sos1_constraints(
&self,
) -> &BTreeMap<crate::Sos1ConstraintID, (Sos1Constraint, RemovedReason)> {
self.sos1_constraint_collection.removed()
}
pub fn sos1_constraint_collection(&self) -> &ConstraintCollection<Sos1Constraint> {
&self.sos1_constraint_collection
}
pub fn sos1_constraint_metadata(&self) -> &ConstraintMetadataStore<crate::Sos1ConstraintID> {
self.sos1_constraint_collection.metadata()
}
pub fn sos1_constraint_metadata_mut(
&mut self,
) -> &mut ConstraintMetadataStore<crate::Sos1ConstraintID> {
self.sos1_constraint_collection.metadata_mut()
}
pub fn required_capabilities(&self) -> Capabilities {
let mut caps = Capabilities::new();
if !self.indicator_constraint_collection.active().is_empty() {
caps.insert(AdditionalCapability::Indicator);
}
if !self.one_hot_constraint_collection.active().is_empty() {
caps.insert(AdditionalCapability::OneHot);
}
if !self.sos1_constraint_collection.active().is_empty() {
caps.insert(AdditionalCapability::Sos1);
}
caps
}
#[tracing::instrument(skip_all)]
pub fn reduce_capabilities(&mut self, supported: &Capabilities) -> crate::Result<Capabilities> {
let mut converted = Capabilities::new();
for cap in [
AdditionalCapability::Indicator,
AdditionalCapability::OneHot,
AdditionalCapability::Sos1,
] {
if supported.contains(&cap) {
continue;
}
let converted_any = match cap {
AdditionalCapability::Indicator => {
if self.indicator_constraint_collection.active().is_empty() {
false
} else {
self.convert_all_indicators_to_constraints()?;
true
}
}
AdditionalCapability::OneHot => {
if self.one_hot_constraint_collection.active().is_empty() {
false
} else {
self.convert_all_one_hots_to_constraints()?;
true
}
}
AdditionalCapability::Sos1 => {
if self.sos1_constraint_collection.active().is_empty() {
false
} else {
self.convert_all_sos1_to_constraints()?;
true
}
}
};
if converted_any {
tracing::info!(
"reduce_capabilities: {cap:?} is not in supported capabilities; converted to regular constraints"
);
converted.insert(cap);
}
}
Ok(converted)
}
}
#[derive(Debug, Clone, PartialEq, getset::Getters, Default)]
pub struct ParametricInstance {
#[getset(get = "pub")]
sense: Sense,
#[getset(get = "pub")]
objective: Function,
#[getset(get = "pub")]
decision_variables: BTreeMap<VariableID, DecisionVariable>,
#[getset(get = "pub")]
parameters: BTreeMap<VariableID, v1::Parameter>,
variable_metadata: VariableMetadataStore,
constraint_collection: ConstraintCollection<Constraint>,
indicator_constraint_collection: ConstraintCollection<IndicatorConstraint>,
one_hot_constraint_collection: ConstraintCollection<OneHotConstraint>,
sos1_constraint_collection: ConstraintCollection<Sos1Constraint>,
#[getset(get = "pub")]
decision_variable_dependency: AcyclicAssignments,
#[getset(get = "pub")]
named_functions: BTreeMap<NamedFunctionID, NamedFunction>,
named_function_metadata: crate::named_function::NamedFunctionMetadataStore,
pub description: Option<v1::instance::Description>,
}
impl ParametricInstance {
pub fn variable_metadata(&self) -> &VariableMetadataStore {
&self.variable_metadata
}
pub fn variable_metadata_mut(&mut self) -> &mut VariableMetadataStore {
&mut self.variable_metadata
}
pub fn named_function_metadata(&self) -> &crate::named_function::NamedFunctionMetadataStore {
&self.named_function_metadata
}
pub fn named_function_metadata_mut(
&mut self,
) -> &mut crate::named_function::NamedFunctionMetadataStore {
&mut self.named_function_metadata
}
pub fn constraints(&self) -> &BTreeMap<ConstraintID, Constraint> {
self.constraint_collection.active()
}
pub fn removed_constraints(&self) -> &BTreeMap<ConstraintID, (Constraint, RemovedReason)> {
self.constraint_collection.removed()
}
pub fn constraint_collection(&self) -> &ConstraintCollection<Constraint> {
&self.constraint_collection
}
pub fn constraint_metadata(&self) -> &ConstraintMetadataStore<ConstraintID> {
self.constraint_collection.metadata()
}
pub fn constraint_metadata_mut(&mut self) -> &mut ConstraintMetadataStore<ConstraintID> {
self.constraint_collection.metadata_mut()
}
pub fn indicator_constraints(
&self,
) -> &BTreeMap<crate::IndicatorConstraintID, IndicatorConstraint> {
self.indicator_constraint_collection.active()
}
pub fn removed_indicator_constraints(
&self,
) -> &BTreeMap<crate::IndicatorConstraintID, (IndicatorConstraint, RemovedReason)> {
self.indicator_constraint_collection.removed()
}
pub fn indicator_constraint_collection(&self) -> &ConstraintCollection<IndicatorConstraint> {
&self.indicator_constraint_collection
}
pub fn indicator_constraint_metadata(
&self,
) -> &ConstraintMetadataStore<crate::IndicatorConstraintID> {
self.indicator_constraint_collection.metadata()
}
pub fn indicator_constraint_metadata_mut(
&mut self,
) -> &mut ConstraintMetadataStore<crate::IndicatorConstraintID> {
self.indicator_constraint_collection.metadata_mut()
}
pub fn one_hot_constraints(&self) -> &BTreeMap<crate::OneHotConstraintID, OneHotConstraint> {
self.one_hot_constraint_collection.active()
}
pub fn removed_one_hot_constraints(
&self,
) -> &BTreeMap<crate::OneHotConstraintID, (OneHotConstraint, RemovedReason)> {
self.one_hot_constraint_collection.removed()
}
pub fn one_hot_constraint_collection(&self) -> &ConstraintCollection<OneHotConstraint> {
&self.one_hot_constraint_collection
}
pub fn one_hot_constraint_metadata(
&self,
) -> &ConstraintMetadataStore<crate::OneHotConstraintID> {
self.one_hot_constraint_collection.metadata()
}
pub fn one_hot_constraint_metadata_mut(
&mut self,
) -> &mut ConstraintMetadataStore<crate::OneHotConstraintID> {
self.one_hot_constraint_collection.metadata_mut()
}
pub fn sos1_constraints(&self) -> &BTreeMap<crate::Sos1ConstraintID, Sos1Constraint> {
self.sos1_constraint_collection.active()
}
pub fn removed_sos1_constraints(
&self,
) -> &BTreeMap<crate::Sos1ConstraintID, (Sos1Constraint, RemovedReason)> {
self.sos1_constraint_collection.removed()
}
pub fn sos1_constraint_collection(&self) -> &ConstraintCollection<Sos1Constraint> {
&self.sos1_constraint_collection
}
pub fn sos1_constraint_metadata(&self) -> &ConstraintMetadataStore<crate::Sos1ConstraintID> {
self.sos1_constraint_collection.metadata()
}
pub fn sos1_constraint_metadata_mut(
&mut self,
) -> &mut ConstraintMetadataStore<crate::Sos1ConstraintID> {
self.sos1_constraint_collection.metadata_mut()
}
}
#[cfg(test)]
mod reduce_capabilities_tests {
use super::*;
use crate::{
indicator_constraint::{IndicatorConstraint, IndicatorConstraintID},
linear,
one_hot_constraint::{OneHotConstraint, OneHotConstraintID},
sos1_constraint::{Sos1Constraint, Sos1ConstraintID},
Bound, DecisionVariable, Equality, Function, Kind, VariableID,
};
use maplit::btreemap;
use std::collections::{BTreeMap, BTreeSet};
fn instance_with_all_capabilities() -> Instance {
let decision_variables = btreemap! {
VariableID::from(0) => DecisionVariable::binary(VariableID::from(0)),
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
VariableID::from(3) => DecisionVariable::binary(VariableID::from(3)),
};
let one_hot = OneHotConstraint::new(
[VariableID::from(0), VariableID::from(1)]
.into_iter()
.collect(),
);
let sos1 = Sos1Constraint::new(
[VariableID::from(2), VariableID::from(3)]
.into_iter()
.collect(),
);
let indicator = IndicatorConstraint::new(
VariableID::from(1),
Equality::LessThanOrEqualToZero,
Function::from(linear!(0)),
);
Instance::builder()
.sense(Sense::Minimize)
.objective(Function::from(linear!(0)))
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.indicator_constraints(BTreeMap::from([(
IndicatorConstraintID::from(1),
indicator,
)]))
.one_hot_constraints(BTreeMap::from([(OneHotConstraintID::from(1), one_hot)]))
.sos1_constraints(BTreeMap::from([(Sos1ConstraintID::from(1), sos1)]))
.build()
.unwrap()
}
#[test]
fn noop_when_all_required_are_supported() {
let mut instance = instance_with_all_capabilities();
let supported: Capabilities = [
AdditionalCapability::Indicator,
AdditionalCapability::OneHot,
AdditionalCapability::Sos1,
]
.into_iter()
.collect();
let before_indicators = instance.indicator_constraints().clone();
let before_one_hots = instance.one_hot_constraints().clone();
let before_sos1 = instance.sos1_constraints().clone();
let converted = instance.reduce_capabilities(&supported).unwrap();
assert!(converted.is_empty());
assert_eq!(instance.indicator_constraints(), &before_indicators);
assert_eq!(instance.one_hot_constraints(), &before_one_hots);
assert_eq!(instance.sos1_constraints(), &before_sos1);
}
#[test]
fn converts_only_unsupported_capabilities() {
let mut instance = instance_with_all_capabilities();
let supported: Capabilities = [AdditionalCapability::Sos1].into_iter().collect();
let converted = instance.reduce_capabilities(&supported).unwrap();
let expected: Capabilities = [
AdditionalCapability::Indicator,
AdditionalCapability::OneHot,
]
.into_iter()
.collect();
assert_eq!(converted, expected);
assert!(instance.indicator_constraints().is_empty());
assert!(instance.one_hot_constraints().is_empty());
assert!(!instance.sos1_constraints().is_empty());
assert!(instance.required_capabilities().is_subset(&supported));
}
#[test]
fn empty_supported_converts_everything() {
let mut instance = instance_with_all_capabilities();
let supported = Capabilities::new();
let converted = instance.reduce_capabilities(&supported).unwrap();
let expected: Capabilities = [
AdditionalCapability::Indicator,
AdditionalCapability::OneHot,
AdditionalCapability::Sos1,
]
.into_iter()
.collect();
assert_eq!(converted, expected);
assert!(instance.required_capabilities().is_empty());
}
#[test]
fn skips_capabilities_that_are_not_required() {
let decision_variables = btreemap! {
VariableID::from(0) => DecisionVariable::binary(VariableID::from(0)),
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
};
let one_hot = OneHotConstraint::new(
[VariableID::from(0), VariableID::from(1)]
.into_iter()
.collect(),
);
let mut instance = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::from(linear!(0)))
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.one_hot_constraints(BTreeMap::from([(OneHotConstraintID::from(1), one_hot)]))
.build()
.unwrap();
let supported = Capabilities::new();
let converted = instance.reduce_capabilities(&supported).unwrap();
let expected: Capabilities = [AdditionalCapability::OneHot].into_iter().collect();
assert_eq!(converted, expected);
assert!(instance.one_hot_constraints().is_empty());
}
#[test]
fn conversion_failure_is_propagated() {
let dv = DecisionVariable::continuous(VariableID::from(0));
let sos1 = Sos1Constraint::new([VariableID::from(0)].into_iter().collect::<BTreeSet<_>>());
let mut instance = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::from(linear!(0)))
.decision_variables(btreemap! { VariableID::from(0) => dv })
.constraints(BTreeMap::new())
.sos1_constraints(BTreeMap::from([(Sos1ConstraintID::from(1), sos1)]))
.build()
.unwrap();
let supported = Capabilities::new();
let err = instance.reduce_capabilities(&supported).unwrap_err();
assert!(err.to_string().contains("non-finite"));
}
#[test]
fn integer_sos1_converts_with_new_indicator() {
let dv = DecisionVariable::new(
VariableID::from(0),
Kind::Integer,
Bound::new(-2.0, 3.0).unwrap(),
None,
crate::ATol::default(),
)
.unwrap();
let sos1 = Sos1Constraint::new([VariableID::from(0)].into_iter().collect::<BTreeSet<_>>());
let mut instance = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::from(linear!(0)))
.decision_variables(btreemap! { VariableID::from(0) => dv })
.constraints(BTreeMap::new())
.sos1_constraints(BTreeMap::from([(Sos1ConstraintID::from(1), sos1)]))
.build()
.unwrap();
let converted = instance.reduce_capabilities(&Capabilities::new()).unwrap();
let expected: Capabilities = [AdditionalCapability::Sos1].into_iter().collect();
assert_eq!(converted, expected);
assert_eq!(instance.decision_variables.len(), 2);
assert!(instance.sos1_constraints().is_empty());
assert!(!instance.constraints().is_empty());
}
}