use super::*;
use crate::{
substitute_acyclic, substitute_one_via_acyclic, Function, Substitute, SubstitutionError,
VariableID,
};
impl Substitute for Instance {
type Output = Self;
fn substitute_acyclic(
mut self,
acyclic: &crate::AcyclicAssignments,
) -> Result<Self::Output, crate::SubstitutionError> {
let substituted_variables: std::collections::BTreeSet<VariableID> =
acyclic.iter().map(|(var_id, _)| *var_id).collect();
let mut affected_constraint_ids = std::collections::BTreeSet::new();
for (constraint_id, constraint) in &self.constraints {
let required_ids = constraint.required_ids();
if !required_ids.is_disjoint(&substituted_variables) {
affected_constraint_ids.insert(*constraint_id);
}
}
substitute_acyclic(&mut self.objective, acyclic)?;
for constraint_id in &affected_constraint_ids {
if let Some(constraint) = self.constraints.get_mut(constraint_id) {
substitute_acyclic(&mut constraint.function, acyclic)?;
}
}
substitute_acyclic(&mut self.decision_variable_dependency, acyclic)?;
self.constraint_hints
.one_hot_constraints
.retain(|hint| !affected_constraint_ids.contains(&hint.id));
self.constraint_hints.sos1_constraints.retain(|hint| {
!affected_constraint_ids.contains(&hint.binary_constraint_id)
&& hint
.big_m_constraint_ids
.is_disjoint(&affected_constraint_ids)
});
Ok(self)
}
fn substitute_one(
self,
assigned: VariableID,
f: &Function,
) -> Result<Self::Output, SubstitutionError> {
substitute_one_via_acyclic(self, assigned, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
coeff,
constraint::Equality,
constraint_hints::{OneHot, Sos1},
linear, DecisionVariable, Sense,
};
use std::collections::BTreeMap;
#[test]
fn test_instance_substitute() {
let mut decision_variables = BTreeMap::new();
decision_variables.insert(
VariableID::from(1),
DecisionVariable::continuous(VariableID::from(1)),
);
decision_variables.insert(
VariableID::from(2),
DecisionVariable::continuous(VariableID::from(2)),
);
let objective = Function::from(linear!(1) + coeff!(2.0) * linear!(2));
let constraint_function = Function::from(linear!(1) + linear!(2) + coeff!(-10.0));
let mut constraints = BTreeMap::new();
let constraint = Constraint {
id: ConstraintID::from(1),
function: constraint_function,
equality: Equality::LessThanOrEqualToZero,
name: None,
subscripts: Vec::new(),
parameters: Default::default(),
description: None,
};
constraints.insert(ConstraintID::from(1), constraint);
let _constraint_hints = ConstraintHints::default();
let instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
let substitution = Function::from(linear!(3) + coeff!(1.0));
let result = instance
.substitute_one(VariableID::from(1), &substitution)
.unwrap();
assert_eq!(result.decision_variable_dependency.len(), 1);
assert!(result
.decision_variable_dependency
.get(&VariableID::from(1))
.is_some());
}
#[test]
fn test_constraint_hints_removal_on_substitute() {
let mut decision_variables = BTreeMap::new();
decision_variables.insert(
VariableID::from(1),
DecisionVariable::binary(VariableID::from(1)),
);
decision_variables.insert(
VariableID::from(2),
DecisionVariable::binary(VariableID::from(2)),
);
decision_variables.insert(
VariableID::from(3),
DecisionVariable::binary(VariableID::from(3)),
);
let mut constraints = BTreeMap::new();
let constraint1_function = Function::from(linear!(1) + linear!(2) + coeff!(-1.0));
let constraint1 = Constraint {
id: ConstraintID::from(1),
function: constraint1_function,
equality: Equality::EqualToZero,
name: None,
subscripts: Vec::new(),
parameters: Default::default(),
description: None,
};
constraints.insert(ConstraintID::from(1), constraint1);
let constraint2_function = Function::from(linear!(2) + linear!(3) + coeff!(-1.0));
let constraint2 = Constraint {
id: ConstraintID::from(2),
function: constraint2_function,
equality: Equality::EqualToZero,
name: None,
subscripts: Vec::new(),
parameters: Default::default(),
description: None,
};
constraints.insert(ConstraintID::from(2), constraint2);
let one_hot_for_constraint1 = OneHot {
id: ConstraintID::from(1),
variables: [VariableID::from(1), VariableID::from(2)]
.into_iter()
.collect(),
};
let one_hot_for_constraint2 = OneHot {
id: ConstraintID::from(2),
variables: [VariableID::from(2), VariableID::from(3)]
.into_iter()
.collect(),
};
let sos1 = Sos1 {
binary_constraint_id: ConstraintID::from(1),
big_m_constraint_ids: [ConstraintID::from(2)].into_iter().collect(),
variables: [
VariableID::from(1),
VariableID::from(2),
VariableID::from(3),
]
.into_iter()
.collect(),
};
let constraint_hints = ConstraintHints {
one_hot_constraints: vec![one_hot_for_constraint1, one_hot_for_constraint2],
sos1_constraints: vec![sos1],
};
let objective = Function::from(linear!(1) + linear!(2) + linear!(3));
let instance = Instance::new(Sense::Minimize, objective, decision_variables, constraints)
.unwrap()
.with_constraint_hints(constraint_hints)
.unwrap();
assert_eq!(instance.constraint_hints.one_hot_constraints.len(), 2);
assert_eq!(instance.constraint_hints.sos1_constraints.len(), 1);
let substitution = Function::from(coeff!(1.0));
let result = instance
.substitute_one(VariableID::from(1), &substitution)
.unwrap();
assert_eq!(result.constraint_hints.one_hot_constraints.len(), 1);
assert_eq!(
result.constraint_hints.one_hot_constraints[0].id,
ConstraintID::from(2)
);
assert_eq!(result.constraint_hints.sos1_constraints.len(), 0);
}
#[test]
fn test_constraint_hints_removal_with_removed_constraints() {
let mut decision_variables = BTreeMap::new();
decision_variables.insert(
VariableID::from(1),
DecisionVariable::binary(VariableID::from(1)),
);
decision_variables.insert(
VariableID::from(2),
DecisionVariable::binary(VariableID::from(2)),
);
decision_variables.insert(
VariableID::from(3),
DecisionVariable::binary(VariableID::from(3)),
);
let mut constraints = BTreeMap::new();
let constraint1_function = Function::from(linear!(1) + linear!(2) + coeff!(-1.0));
let constraint1 = Constraint {
id: ConstraintID::from(1),
function: constraint1_function,
equality: Equality::EqualToZero,
name: None,
subscripts: Vec::new(),
parameters: Default::default(),
description: None,
};
constraints.insert(ConstraintID::from(1), constraint1);
let mut removed_constraints = BTreeMap::new();
let removed_constraint_function = Function::from(linear!(1) + linear!(3) + coeff!(-1.0));
let removed_constraint_inner = Constraint {
id: ConstraintID::from(2),
function: removed_constraint_function,
equality: Equality::EqualToZero,
name: None,
subscripts: Vec::new(),
parameters: Default::default(),
description: None,
};
let removed_constraint = RemovedConstraint {
constraint: removed_constraint_inner,
removed_reason: "test".to_string(),
removed_reason_parameters: Default::default(),
};
removed_constraints.insert(ConstraintID::from(2), removed_constraint);
let one_hot_for_active = OneHot {
id: ConstraintID::from(1),
variables: [VariableID::from(1), VariableID::from(2)]
.into_iter()
.collect(),
};
let one_hot_for_removed = OneHot {
id: ConstraintID::from(2),
variables: [VariableID::from(1), VariableID::from(3)]
.into_iter()
.collect(),
};
let constraint_hints = ConstraintHints {
one_hot_constraints: vec![one_hot_for_active, one_hot_for_removed],
sos1_constraints: vec![],
};
let objective = Function::from(linear!(1) + linear!(2) + linear!(3));
let mut instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
instance.removed_constraints = removed_constraints;
instance.constraint_hints = constraint_hints;
assert_eq!(instance.constraint_hints.one_hot_constraints.len(), 2);
let substitution = Function::from(coeff!(1.0));
let result = instance
.substitute_one(VariableID::from(1), &substitution)
.unwrap();
assert_eq!(result.constraint_hints.one_hot_constraints.len(), 1);
assert_eq!(
result.constraint_hints.one_hot_constraints[0].id,
ConstraintID::from(2)
);
}
}