mod analysis;
mod approx;
pub(crate) mod arbitrary;
mod builder;
mod clip_bounds;
mod convert;
mod decision_variable;
mod encoding;
mod evaluate;
mod format_function;
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;
mod unary_encode;
pub use analysis::*;
pub use arbitrary::{InstanceParameters, InstanceSpace};
pub use builder::*;
pub use parametric_builder::*;
pub use stats::*;
use crate::{
constraint::{ConstraintContextStore, RemovedReason},
constraint_type::ConstraintCollection,
decision_variable::{DecisionVariableTable, VariableLabelStore},
indicator_constraint::IndicatorConstraint,
named_function::NamedFunctionID,
one_hot_constraint::OneHotConstraint,
parameter::ParameterTable,
sos1_constraint::Sos1Constraint,
v1, AcyclicAssignments, Constraint, ConstraintContext, ConstraintID, DecisionVariable,
Evaluate, Function, ModelingLabel, NamedFunction, NamedFunctionTable, VariableID,
VariableIDSet,
};
use std::collections::{BTreeMap, HashMap};
#[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,
crate::logical_memory::LogicalMemoryProfile,
)]
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,
decision_variables: DecisionVariableTable,
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,
named_functions: NamedFunctionTable<NamedFunction>,
pub parameters: Option<v1::Parameters>,
pub description: Option<v1::instance::Description>,
pub annotations: HashMap<String, String>,
}
impl Instance {
pub fn decision_variable_table(&self) -> &DecisionVariableTable {
&self.decision_variables
}
pub fn decision_variables(&self) -> &BTreeMap<VariableID, DecisionVariable> {
self.decision_variables.entries()
}
pub fn variable_labels(&self) -> &VariableLabelStore {
self.decision_variables.labels()
}
pub fn set_variable_label(
&mut self,
id: VariableID,
label: ModelingLabel,
) -> crate::Result<()> {
self.decision_variables.set_label(id, label)
}
pub fn fixed_decision_variable_values(&self) -> &BTreeMap<VariableID, f64> {
self.decision_variables.fixed_values()
}
pub fn fixed_decision_variable_value(&self, id: VariableID) -> Option<f64> {
self.decision_variables.fixed_value(id)
}
pub fn named_function_table(&self) -> &NamedFunctionTable<NamedFunction> {
&self.named_functions
}
pub fn named_functions(&self) -> &BTreeMap<NamedFunctionID, NamedFunction> {
self.named_functions.entries()
}
pub fn named_function_labels(&self) -> &crate::named_function::NamedFunctionLabelStore {
self.named_functions.labels()
}
pub fn set_named_function_label(
&mut self,
id: NamedFunctionID,
label: ModelingLabel,
) -> crate::Result<()> {
self.named_functions.set_label(id, label)
}
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_context(&self) -> &ConstraintContextStore<ConstraintID> {
self.constraint_collection.context()
}
pub fn set_constraint_context(
&mut self,
id: ConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.constraint_collection
.set_context_for_owner(id, context, "constraint")
}
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_context(
&self,
) -> &ConstraintContextStore<crate::IndicatorConstraintID> {
self.indicator_constraint_collection.context()
}
pub fn set_indicator_constraint_context(
&mut self,
id: crate::IndicatorConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.indicator_constraint_collection.set_context_for_owner(
id,
context,
"indicator constraint",
)
}
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_context(&self) -> &ConstraintContextStore<crate::OneHotConstraintID> {
self.one_hot_constraint_collection.context()
}
pub fn set_one_hot_constraint_context(
&mut self,
id: crate::OneHotConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.one_hot_constraint_collection
.set_context_for_owner(id, context, "one-hot constraint")
}
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_context(&self) -> &ConstraintContextStore<crate::Sos1ConstraintID> {
self.sos1_constraint_collection.context()
}
pub fn set_sos1_constraint_context(
&mut self,
id: crate::Sos1ConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.sos1_constraint_collection
.set_context_for_owner(id, context, "SOS1 constraint")
}
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,
decision_variables: DecisionVariableTable,
#[getset(get = "pub")]
parameters: ParameterTable,
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,
named_functions: NamedFunctionTable<NamedFunction>,
pub description: Option<v1::instance::Description>,
pub annotations: HashMap<String, String>,
}
impl ParametricInstance {
pub fn decision_variable_table(&self) -> &DecisionVariableTable {
&self.decision_variables
}
pub fn decision_variables(&self) -> &BTreeMap<VariableID, DecisionVariable> {
self.decision_variables.entries()
}
pub fn variable_labels(&self) -> &VariableLabelStore {
self.decision_variables.labels()
}
pub fn set_variable_label(
&mut self,
id: VariableID,
label: ModelingLabel,
) -> crate::Result<()> {
self.decision_variables.set_label(id, label)
}
pub fn fixed_decision_variable_values(&self) -> &BTreeMap<VariableID, f64> {
self.decision_variables.fixed_values()
}
pub fn fixed_decision_variable_value(&self, id: VariableID) -> Option<f64> {
self.decision_variables.fixed_value(id)
}
pub fn named_function_table(&self) -> &NamedFunctionTable<NamedFunction> {
&self.named_functions
}
pub fn named_functions(&self) -> &BTreeMap<NamedFunctionID, NamedFunction> {
self.named_functions.entries()
}
pub fn named_function_labels(&self) -> &crate::named_function::NamedFunctionLabelStore {
self.named_functions.labels()
}
pub fn set_named_function_label(
&mut self,
id: NamedFunctionID,
label: ModelingLabel,
) -> crate::Result<()> {
self.named_functions.set_label(id, label)
}
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_context(&self) -> &ConstraintContextStore<ConstraintID> {
self.constraint_collection.context()
}
pub fn set_constraint_context(
&mut self,
id: ConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.constraint_collection
.set_context_for_owner(id, context, "constraint")
}
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_context(
&self,
) -> &ConstraintContextStore<crate::IndicatorConstraintID> {
self.indicator_constraint_collection.context()
}
pub fn set_indicator_constraint_context(
&mut self,
id: crate::IndicatorConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.indicator_constraint_collection.set_context_for_owner(
id,
context,
"indicator constraint",
)
}
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_context(&self) -> &ConstraintContextStore<crate::OneHotConstraintID> {
self.one_hot_constraint_collection.context()
}
pub fn set_one_hot_constraint_context(
&mut self,
id: crate::OneHotConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.one_hot_constraint_collection
.set_context_for_owner(id, context, "one-hot constraint")
}
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_context(&self) -> &ConstraintContextStore<crate::Sos1ConstraintID> {
self.sos1_constraint_collection.context()
}
pub fn set_sos1_constraint_context(
&mut self,
id: crate::Sos1ConstraintID,
context: ConstraintContext,
) -> crate::Result<()> {
self.sos1_constraint_collection
.set_context_for_owner(id, context, "SOS1 constraint")
}
}
#[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(1) => DecisionVariable::binary(),
VariableID::from(2) => DecisionVariable::binary(),
VariableID::from(3) => DecisionVariable::binary(),
};
let one_hot = OneHotConstraint::new(
[VariableID::from(0), VariableID::from(1)]
.into_iter()
.collect(),
)
.unwrap();
let sos1 = Sos1Constraint::new(
[VariableID::from(2), VariableID::from(3)]
.into_iter()
.collect(),
)
.unwrap();
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(1) => DecisionVariable::binary(),
};
let one_hot = OneHotConstraint::new(
[VariableID::from(0), VariableID::from(1)]
.into_iter()
.collect(),
)
.unwrap();
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();
let sos1 = Sos1Constraint::new([VariableID::from(0)].into_iter().collect::<BTreeSet<_>>())
.unwrap();
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(
Kind::Integer,
Bound::new(-2.0, 3.0).unwrap(),
crate::ATol::default(),
)
.unwrap();
let sos1 = Sos1Constraint::new([VariableID::from(0)].into_iter().collect::<BTreeSet<_>>())
.unwrap();
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());
}
}