use super::*;
use crate::parse::Parse;
#[derive(Debug, Clone, Default)]
pub struct ParametricInstanceBuilder {
sense: Option<Sense>,
objective: Option<Function>,
decision_variables: Option<BTreeMap<VariableID, DecisionVariable>>,
parameters: Option<BTreeMap<VariableID, v1::Parameter>>,
constraints: Option<BTreeMap<ConstraintID, Constraint>>,
named_functions: BTreeMap<NamedFunctionID, NamedFunction>,
removed_constraints: BTreeMap<ConstraintID, RemovedConstraint>,
decision_variable_dependency: AcyclicAssignments,
constraint_hints: ConstraintHints,
description: Option<v1::instance::Description>,
}
impl ParametricInstanceBuilder {
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 parameters(mut self, parameters: BTreeMap<VariableID, v1::Parameter>) -> Self {
self.parameters = Some(parameters);
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 description(mut self, description: v1::instance::Description) -> Self {
self.description = Some(description);
self
}
pub fn build(self) -> anyhow::Result<ParametricInstance> {
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 parameters = self.parameters.ok_or(InstanceError::MissingRequiredField {
field: "parameters",
})?;
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 ¶meters {
if key.into_inner() != value.id {
return Err(InstanceError::InconsistentParameterID {
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 decision_variable_ids: VariableIDSet = decision_variables.keys().cloned().collect();
let parameter_ids: VariableIDSet = parameters.keys().cloned().collect();
let intersection: VariableIDSet = decision_variable_ids
.intersection(¶meter_ids)
.cloned()
.collect();
if !intersection.is_empty() {
return Err(InstanceError::DuplicatedVariableID {
id: *intersection.iter().next().unwrap(),
}
.into());
}
let all_variable_ids: VariableIDSet = decision_variable_ids
.union(¶meter_ids)
.cloned()
.collect();
for id in objective.required_ids() {
if !all_variable_ids.contains(&id) {
return Err(InstanceError::UndefinedVariableID { id }.into());
}
}
for constraint in constraints.values() {
for id in constraint.required_ids() {
if !all_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 !all_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 !all_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 !decision_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(ParametricInstance {
sense,
objective,
decision_variables,
parameters,
constraints,
named_functions: self.named_functions,
removed_constraints,
decision_variable_dependency: self.decision_variable_dependency,
constraint_hints,
description: self.description,
})
}
}
impl ParametricInstance {
pub fn builder() -> ParametricInstanceBuilder {
ParametricInstanceBuilder::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parametric_builder_basic() {
let instance = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.parameters(BTreeMap::new())
.constraints(BTreeMap::new())
.build()
.unwrap();
assert_eq!(*instance.sense(), Sense::Minimize);
assert!(instance.decision_variables().is_empty());
assert!(instance.parameters().is_empty());
assert!(instance.constraints().is_empty());
}
#[test]
fn test_parametric_builder_missing_required_field() {
let err = ParametricInstance::builder()
.sense(Sense::Minimize)
.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: "parameters"
}
));
}
#[test]
fn test_parametric_builder_overlapping_ids() {
use maplit::btreemap;
let var_id = VariableID::from(1);
let err = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(btreemap! {
var_id => DecisionVariable::binary(var_id),
})
.parameters(btreemap! {
var_id => v1::Parameter { id: 1, ..Default::default() },
})
.constraints(BTreeMap::new())
.build()
.unwrap_err();
let instance_err = err.downcast_ref::<InstanceError>().unwrap();
assert!(matches!(
instance_err,
InstanceError::DuplicatedVariableID { id } if *id == var_id
));
}
#[test]
fn test_parametric_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 = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.parameters(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_parametric_builder_undefined_variable_in_named_function() {
use crate::{coeff, linear, 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 = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(BTreeMap::new())
.parameters(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)
));
}
#[test]
fn test_parametric_builder_named_function_with_parameter() {
use crate::{linear, NamedFunction, NamedFunctionID};
use maplit::btreemap;
let var_id = VariableID::from(1);
let param_id = VariableID::from(2);
let named_function = NamedFunction {
id: NamedFunctionID::from(1),
function: Function::from(linear!(1) + linear!(2)), name: Some("f".to_string()),
subscripts: vec![],
parameters: Default::default(),
description: None,
};
let instance = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(btreemap! {
var_id => DecisionVariable::binary(var_id),
})
.parameters(btreemap! {
param_id => v1::Parameter { id: 2, ..Default::default() },
})
.constraints(BTreeMap::new())
.named_functions(btreemap! {
NamedFunctionID::from(1) => named_function,
})
.build()
.unwrap();
assert_eq!(instance.named_functions().len(), 1);
}
}