use super::*;
use crate::parse::Parse;
#[derive(Debug, Clone, Default)]
pub struct InstanceBuilder {
sense: Option<Sense>,
objective: Option<Function>,
decision_variables: Option<BTreeMap<VariableID, DecisionVariable>>,
constraints: Option<BTreeMap<ConstraintID, Constraint>>,
named_functions: BTreeMap<NamedFunctionID, NamedFunction>,
removed_constraints: BTreeMap<ConstraintID, RemovedConstraint>,
decision_variable_dependency: AcyclicAssignments,
constraint_hints: ConstraintHints,
parameters: Option<v1::Parameters>,
description: Option<v1::instance::Description>,
}
impl InstanceBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn sense(mut self, sense: Sense) -> Self {
self.sense = Some(sense);
self
}
pub fn objective(mut self, objective: Function) -> Self {
self.objective = Some(objective);
self
}
pub fn decision_variables(
mut self,
decision_variables: BTreeMap<VariableID, DecisionVariable>,
) -> Self {
self.decision_variables = Some(decision_variables);
self
}
pub fn constraints(mut self, constraints: BTreeMap<ConstraintID, Constraint>) -> Self {
self.constraints = Some(constraints);
self
}
pub fn named_functions(
mut self,
named_functions: BTreeMap<NamedFunctionID, NamedFunction>,
) -> Self {
self.named_functions = named_functions;
self
}
pub fn removed_constraints(
mut self,
removed_constraints: BTreeMap<ConstraintID, RemovedConstraint>,
) -> Self {
self.removed_constraints = removed_constraints;
self
}
pub fn decision_variable_dependency(
mut self,
decision_variable_dependency: AcyclicAssignments,
) -> Self {
self.decision_variable_dependency = decision_variable_dependency;
self
}
pub fn constraint_hints(mut self, constraint_hints: ConstraintHints) -> Self {
self.constraint_hints = constraint_hints;
self
}
pub fn parameters(mut self, parameters: v1::Parameters) -> Self {
self.parameters = Some(parameters);
self
}
pub fn description(mut self, description: v1::instance::Description) -> Self {
self.description = Some(description);
self
}
pub fn build(self) -> anyhow::Result<Instance> {
let sense = self
.sense
.ok_or(InstanceError::MissingRequiredField { field: "sense" })?;
let objective = self
.objective
.ok_or(InstanceError::MissingRequiredField { field: "objective" })?;
let decision_variables =
self.decision_variables
.ok_or(InstanceError::MissingRequiredField {
field: "decision_variables",
})?;
let constraints = self
.constraints
.ok_or(InstanceError::MissingRequiredField {
field: "constraints",
})?;
for (key, value) in &decision_variables {
if *key != value.id() {
return Err(InstanceError::InconsistentDecisionVariableID {
key: *key,
value_id: value.id(),
}
.into());
}
}
for (key, value) in &constraints {
if *key != value.id {
return Err(InstanceError::InconsistentConstraintID {
key: *key,
value_id: value.id,
}
.into());
}
}
for (key, value) in &self.removed_constraints {
if *key != value.constraint.id {
return Err(InstanceError::InconsistentRemovedConstraintID {
key: *key,
value_id: value.constraint.id,
}
.into());
}
}
let variable_ids: VariableIDSet = decision_variables.keys().cloned().collect();
for id in objective.required_ids() {
if !variable_ids.contains(&id) {
return Err(InstanceError::UndefinedVariableID { id }.into());
}
}
for constraint in constraints.values() {
for id in constraint.required_ids() {
if !variable_ids.contains(&id) {
return Err(InstanceError::UndefinedVariableID { id }.into());
}
}
}
for removed in self.removed_constraints.values() {
for id in removed.constraint.required_ids() {
if !variable_ids.contains(&id) {
return Err(InstanceError::UndefinedVariableID { id }.into());
}
}
}
for (key, nf) in &self.named_functions {
if *key != nf.id {
return Err(InstanceError::InconsistentNamedFunctionID {
key: *key,
id: nf.id,
}
.into());
}
for id in nf.function.required_ids() {
if !variable_ids.contains(&id) {
return Err(InstanceError::UndefinedVariableID { id }.into());
}
}
}
for id in self.removed_constraints.keys() {
if constraints.contains_key(id) {
return Err(InstanceError::OverlappingConstraintID { id: *id }.into());
}
}
for id in self.decision_variable_dependency.keys() {
if !variable_ids.contains(&id) {
return Err(InstanceError::UndefinedDependentVariableID { id }.into());
}
}
let mut used: VariableIDSet = objective.required_ids().into_iter().collect();
for constraint in constraints.values() {
used.extend(constraint.required_ids());
}
let fixed: VariableIDSet = decision_variables
.values()
.filter(|dv| dv.substituted_value().is_some())
.map(|dv| dv.id())
.collect();
let dependent: VariableIDSet = self.decision_variable_dependency.keys().collect();
if let Some(id) = used.intersection(&dependent).next() {
return Err(InstanceError::DependentVariableUsed { id: *id }.into());
}
if let Some(id) = used.intersection(&fixed).next() {
return Err(InstanceError::FixedVariableUsed { id: *id }.into());
}
if let Some(id) = fixed.intersection(&dependent).next() {
return Err(InstanceError::FixedAndDependentVariable { id: *id }.into());
}
let hints: v1::ConstraintHints = self.constraint_hints.into();
let context = (decision_variables, constraints, self.removed_constraints);
let constraint_hints = hints.parse(&context)?;
let (decision_variables, constraints, removed_constraints) = context;
Ok(Instance {
sense,
objective,
decision_variables,
constraints,
named_functions: self.named_functions,
removed_constraints,
decision_variable_dependency: self.decision_variable_dependency,
constraint_hints,
parameters: self.parameters,
description: self.description,
})
}
}
impl Instance {
pub fn builder() -> InstanceBuilder {
InstanceBuilder::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{coeff, linear};
#[test]
fn test_builder_basic() {
let instance = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.build()
.unwrap();
assert_eq!(instance.sense(), Sense::Minimize);
assert!(instance.decision_variables().is_empty());
assert!(instance.constraints().is_empty());
}
#[test]
fn test_builder_missing_required_field() {
let err = Instance::builder()
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::MissingRequiredField { field: "sense" }
));
let err = Instance::builder()
.sense(Sense::Minimize)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::MissingRequiredField { field: "objective" }
));
}
#[test]
fn test_builder_with_optional_fields() {
let params = v1::Parameters::default();
let desc = v1::instance::Description::default();
let instance = Instance::builder()
.sense(Sense::Maximize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.parameters(params.clone())
.description(desc.clone())
.build()
.unwrap();
assert_eq!(instance.sense(), Sense::Maximize);
assert!(instance.parameters.is_some());
assert!(instance.description.is_some());
}
#[test]
fn test_builder_undefined_variable_in_objective() {
let objective = (linear!(999) + coeff!(1.0)).into();
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(objective)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::UndefinedVariableID {
id
} if *id == VariableID::from(999)
));
}
#[test]
fn test_builder_overlapping_constraint_ids() {
use crate::{Constraint, RemovedConstraint};
use maplit::btreemap;
let constraint_id = ConstraintID::from(1);
let constraint = Constraint::equal_to_zero(constraint_id, Function::Zero);
let removed_constraint = RemovedConstraint {
constraint: constraint.clone(),
removed_reason: "test".to_string(),
removed_reason_parameters: Default::default(),
};
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(btreemap! { constraint_id => constraint })
.removed_constraints(btreemap! { constraint_id => removed_constraint })
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::OverlappingConstraintID { id } if *id == constraint_id
));
}
#[test]
fn test_builder_undefined_variable_dependency() {
use maplit::btreemap;
let var_id = VariableID::from(1);
let undefined_var_id = VariableID::from(999);
let decision_variables = btreemap! {
var_id => DecisionVariable::binary(var_id),
};
let dependency = AcyclicAssignments::new(btreemap! {
undefined_var_id => Function::Zero,
})
.unwrap();
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.decision_variable_dependency(dependency)
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::UndefinedDependentVariableID { id } if *id == undefined_var_id
));
}
#[test]
fn test_builder_valid_variable_dependency() {
use maplit::btreemap;
let var_id = VariableID::from(1);
let decision_variables = btreemap! {
var_id => DecisionVariable::binary(var_id),
};
let dependency = AcyclicAssignments::new(btreemap! {
var_id => Function::Zero,
})
.unwrap();
let instance = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.decision_variable_dependency(dependency)
.build();
assert!(instance.is_ok());
}
#[test]
fn test_builder_dependent_variable_used_in_objective() {
use crate::linear;
use maplit::btreemap;
let var_id = VariableID::from(1);
let decision_variables = btreemap! {
var_id => DecisionVariable::binary(var_id),
};
let dependency = AcyclicAssignments::new(btreemap! {
var_id => Function::Zero,
})
.unwrap();
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::from(linear!(1)))
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.decision_variable_dependency(dependency)
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::DependentVariableUsed { id } if *id == var_id
));
}
#[test]
fn test_builder_dependent_variable_used_in_constraint() {
use crate::linear;
use maplit::btreemap;
let var_id = VariableID::from(1);
let constraint_id = ConstraintID::from(1);
let decision_variables = btreemap! {
var_id => DecisionVariable::binary(var_id),
};
let dependency = AcyclicAssignments::new(btreemap! {
var_id => Function::Zero,
})
.unwrap();
let constraint = Constraint::equal_to_zero(constraint_id, Function::from(linear!(1)));
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(decision_variables)
.constraints(btreemap! { constraint_id => constraint })
.decision_variable_dependency(dependency)
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::DependentVariableUsed { id } if *id == var_id
));
}
#[test]
fn test_builder_fixed_and_dependent_variable() {
use maplit::btreemap;
let var_id = VariableID::from(1);
let mut dv = DecisionVariable::binary(var_id);
dv.substitute(1.0, crate::ATol::default()).unwrap();
let decision_variables = btreemap! {
var_id => dv,
};
let dependency = AcyclicAssignments::new(btreemap! {
var_id => Function::Zero,
})
.unwrap();
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.decision_variable_dependency(dependency)
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::FixedAndDependentVariable { id } if *id == var_id
));
}
#[test]
fn test_builder_undefined_variable_in_removed_constraint() {
use crate::RemovedConstraint;
use maplit::btreemap;
let constraint_id = ConstraintID::from(1);
let constraint =
Constraint::equal_to_zero(constraint_id, Function::from(linear!(999) + coeff!(1.0)));
let removed_constraint = RemovedConstraint {
constraint,
removed_reason: "test".to_string(),
removed_reason_parameters: Default::default(),
};
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.removed_constraints(btreemap! { constraint_id => removed_constraint })
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::UndefinedVariableID { id } if *id == VariableID::from(999)
));
}
#[test]
fn test_builder_fixed_variable_used_in_objective() {
use maplit::btreemap;
let var_id = VariableID::from(1);
let mut dv = DecisionVariable::binary(var_id);
dv.substitute(1.0, crate::ATol::default()).unwrap();
let decision_variables = btreemap! {
var_id => dv,
};
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::from(linear!(1)))
.decision_variables(decision_variables)
.constraints(BTreeMap::new())
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::FixedVariableUsed { id } if *id == var_id
));
}
#[test]
fn test_builder_inconsistent_named_function_id() {
use crate::{NamedFunction, NamedFunctionID};
use maplit::btreemap;
let named_function = NamedFunction {
id: NamedFunctionID::from(1),
function: Function::Zero,
name: Some("f".to_string()),
subscripts: vec![],
parameters: Default::default(),
description: None,
};
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.named_functions(btreemap! {
NamedFunctionID::from(2) => named_function, })
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::InconsistentNamedFunctionID { key, id }
if *key == NamedFunctionID::from(2) && *id == NamedFunctionID::from(1)
));
}
#[test]
fn test_builder_undefined_variable_in_named_function() {
use crate::{NamedFunction, NamedFunctionID};
use maplit::btreemap;
let named_function = NamedFunction {
id: NamedFunctionID::from(1),
function: Function::from(linear!(999) + coeff!(1.0)),
name: Some("f".to_string()),
subscripts: vec![],
parameters: Default::default(),
description: None,
};
let err = Instance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.constraints(BTreeMap::new())
.named_functions(btreemap! {
NamedFunctionID::from(1) => named_function,
})
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::UndefinedVariableID { id } if *id == VariableID::from(999)
));
}
}