use super::*;
impl Instance {
fn validate_required_ids_with_sets(
required_ids: &VariableIDSet,
variable_ids: &VariableIDSet,
dependency_keys: &VariableIDSet,
fixed_ids: &VariableIDSet,
) -> crate::Result<()> {
if !required_ids.is_subset(variable_ids) {
let id = *required_ids.difference(variable_ids).next().unwrap();
crate::bail!({ ?id }, "Undefined variable ID is used: {id:?}");
}
if let Some(&id) = required_ids.intersection(dependency_keys).next() {
crate::bail!(
{ ?id },
"Dependent variable cannot be used in objectives or constraints: {id:?}",
);
}
if let Some(&id) = required_ids.intersection(fixed_ids).next() {
crate::bail!(
{ ?id },
"Fixed variable {id:?} (substituted_value set) cannot be used in objectives or constraints",
);
}
Ok(())
}
fn validate_required_ids(&self, required_ids: VariableIDSet) -> crate::Result<()> {
let variable_ids: VariableIDSet = self.decision_variables.keys().cloned().collect();
let dependency_keys: VariableIDSet = self.decision_variable_dependency.keys().collect();
let fixed_ids: VariableIDSet = self
.decision_variables
.values()
.filter(|dv| dv.substituted_value().is_some())
.map(|dv| dv.id())
.collect();
Self::validate_required_ids_with_sets(
&required_ids,
&variable_ids,
&dependency_keys,
&fixed_ids,
)
}
pub fn set_objective(&mut self, objective: Function) -> crate::Result<()> {
self.validate_required_ids(objective.required_ids())?;
self.objective = objective;
Ok(())
}
pub fn add_constraint(
&mut self,
constraint: Constraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<ConstraintID> {
self.validate_required_ids(constraint.required_ids())?;
let id = self.constraint_collection.unused_id();
self.constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
fn require_binary_variable(&self, id: VariableID) -> crate::Result<()> {
let dv = self
.decision_variables
.get(&id)
.ok_or_else(|| crate::error!("Variable {id:?} is not defined in decision_variables"))?;
if dv.kind() != crate::decision_variable::Kind::Binary {
crate::bail!({ ?id }, "Variable {id:?} must be binary");
}
Ok(())
}
pub fn add_indicator_constraint(
&mut self,
constraint: crate::IndicatorConstraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<crate::IndicatorConstraintID> {
self.validate_required_ids(constraint.required_ids())?;
self.require_binary_variable(constraint.indicator_variable)?;
let id = self.indicator_constraint_collection.unused_id();
self.indicator_constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
pub fn add_one_hot_constraint(
&mut self,
constraint: crate::OneHotConstraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<crate::OneHotConstraintID> {
self.validate_required_ids(constraint.required_ids())?;
for var_id in &constraint.variables {
self.require_binary_variable(*var_id)?;
}
let id = self.one_hot_constraint_collection.unused_id();
self.one_hot_constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
pub fn add_sos1_constraint(
&mut self,
constraint: crate::Sos1Constraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<crate::Sos1ConstraintID> {
if constraint.variables.is_empty() {
crate::bail!("SOS1 constraint must contain at least one variable");
}
self.validate_required_ids(constraint.required_ids())?;
let id = self.sos1_constraint_collection.unused_id();
self.sos1_constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
pub fn add_decision_variable(
&mut self,
variable: crate::DecisionVariable,
metadata: crate::DecisionVariableMetadata,
) -> crate::Result<crate::VariableID> {
let id = variable.id();
if self.decision_variables.contains_key(&id) {
crate::bail!({ ?id }, "Duplicate decision variable ID: {id:?}");
}
if self.decision_variable_dependency.keys().any(|k| k == id) {
crate::bail!(
{ ?id },
"Variable id {id:?} is currently used as a substitution-dependency key",
);
}
self.decision_variables.insert(id, variable);
self.variable_metadata.insert(id, metadata);
Ok(id)
}
pub fn insert_constraint(
&mut self,
id: ConstraintID,
constraint: Constraint,
) -> crate::Result<Option<Constraint>> {
self.validate_required_ids(constraint.required_ids())?;
use std::collections::btree_map::Entry;
if let Entry::Occupied(mut o) = self.constraint_collection.removed_mut().entry(id) {
let (rc, _reason) = o.get_mut();
let old_function = std::mem::replace(&mut rc.stage.function, constraint.stage.function);
let old_equality = std::mem::replace(&mut rc.equality, constraint.equality);
let removed = Constraint {
equality: old_equality,
stage: crate::constraint::CreatedData {
function: old_function,
},
};
return Ok(Some(removed));
}
Ok(self
.constraint_collection
.active_mut()
.insert(id, constraint))
}
pub fn insert_constraints(
&mut self,
constraints: Vec<(ConstraintID, Constraint)>,
) -> crate::Result<BTreeMap<ConstraintID, Constraint>> {
let variable_ids: VariableIDSet = self.decision_variables.keys().cloned().collect();
let dependency_keys: VariableIDSet = self.decision_variable_dependency.keys().collect();
let fixed_ids: VariableIDSet = self
.decision_variables
.values()
.filter(|dv| dv.substituted_value().is_some())
.map(|dv| dv.id())
.collect();
for (_, constraint) in &constraints {
let required_ids = constraint.required_ids();
Self::validate_required_ids_with_sets(
&required_ids,
&variable_ids,
&dependency_keys,
&fixed_ids,
)?;
}
let mut replaced = BTreeMap::new();
for (id, constraint) in constraints {
use std::collections::btree_map::Entry;
let old = if let Entry::Occupied(mut o) =
self.constraint_collection.removed_mut().entry(id)
{
let (rc, _reason) = o.get_mut();
let old_function =
std::mem::replace(&mut rc.stage.function, constraint.stage.function);
let old_equality = std::mem::replace(&mut rc.equality, constraint.equality);
Some(Constraint {
equality: old_equality,
stage: crate::constraint::CreatedData {
function: old_function,
},
})
} else {
self.constraint_collection
.active_mut()
.insert(id, constraint)
};
if let Some(old_constraint) = old {
replaced.insert(id, old_constraint);
}
}
Ok(replaced)
}
pub fn next_constraint_id(&self) -> ConstraintID {
let max_in_constraints = self
.constraints()
.last_key_value()
.map(|(id, _)| id.into_inner());
let max_in_removed = self
.removed_constraints()
.last_key_value()
.map(|(id, _)| id.into_inner());
max_in_constraints
.max(max_in_removed)
.map(|max| ConstraintID::from(max + 1))
.unwrap_or(ConstraintID::from(0))
}
}
impl ParametricInstance {
fn validate_required_ids(&self, required_ids: VariableIDSet) -> crate::Result<()> {
let variable_ids: VariableIDSet = self.decision_variables().keys().cloned().collect();
let parameter_ids: VariableIDSet = self.parameters().keys().cloned().collect();
let known_ids: VariableIDSet = variable_ids.union(¶meter_ids).cloned().collect();
let dependency_keys: VariableIDSet = self.decision_variable_dependency().keys().collect();
let fixed_ids: VariableIDSet = self
.decision_variables()
.values()
.filter(|dv| dv.substituted_value().is_some())
.map(|dv| dv.id())
.collect();
if !required_ids.is_subset(&known_ids) {
let id = *required_ids.difference(&known_ids).next().unwrap();
crate::bail!({ ?id }, "Undefined variable ID is used: {id:?}");
}
if let Some(&id) = required_ids.intersection(&dependency_keys).next() {
crate::bail!(
{ ?id },
"Dependent variable cannot be used in objectives or constraints: {id:?}",
);
}
if let Some(&id) = required_ids.intersection(&fixed_ids).next() {
crate::bail!(
{ ?id },
"Fixed variable {id:?} (substituted_value set) cannot be used in objectives or constraints",
);
}
Ok(())
}
pub fn add_constraint(
&mut self,
constraint: Constraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<ConstraintID> {
self.validate_required_ids(constraint.required_ids())?;
let id = self.constraint_collection.unused_id();
self.constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
fn require_decision_variables(&self, ids: VariableIDSet) -> crate::Result<()> {
let variable_ids: VariableIDSet = self.decision_variables().keys().cloned().collect();
if !ids.is_subset(&variable_ids) {
let id = *ids.difference(&variable_ids).next().unwrap();
if self.parameters().contains_key(&id) {
crate::bail!(
{ ?id },
"Parameter id {id:?} cannot occupy a structural variable position; \
structural variables in indicator / one-hot / SOS1 constraints \
must be decision variables",
);
}
crate::bail!({ ?id }, "Undefined variable ID is used: {id:?}");
}
Ok(())
}
fn require_binary_variable(&self, id: VariableID) -> crate::Result<()> {
let dv = self.decision_variables().get(&id).ok_or_else(|| {
if self.parameters().contains_key(&id) {
crate::error!(
"Parameter id {id:?} cannot occupy a structural variable position; \
it must be a binary decision variable",
)
} else {
crate::error!("Variable {id:?} is not defined in decision_variables")
}
})?;
if dv.kind() != crate::decision_variable::Kind::Binary {
crate::bail!({ ?id }, "Variable {id:?} must be binary");
}
Ok(())
}
pub fn add_indicator_constraint(
&mut self,
constraint: crate::IndicatorConstraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<crate::IndicatorConstraintID> {
self.require_binary_variable(constraint.indicator_variable)?;
self.validate_required_ids(constraint.required_ids())?;
let id = self.indicator_constraint_collection.unused_id();
self.indicator_constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
pub fn add_one_hot_constraint(
&mut self,
constraint: crate::OneHotConstraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<crate::OneHotConstraintID> {
for var_id in &constraint.variables {
self.require_binary_variable(*var_id)?;
}
self.validate_required_ids(constraint.required_ids())?;
let id = self.one_hot_constraint_collection.unused_id();
self.one_hot_constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
pub fn add_sos1_constraint(
&mut self,
constraint: crate::Sos1Constraint,
metadata: crate::ConstraintMetadata,
) -> crate::Result<crate::Sos1ConstraintID> {
if constraint.variables.is_empty() {
crate::bail!("SOS1 constraint must contain at least one variable");
}
let required_ids = constraint.required_ids();
self.require_decision_variables(required_ids.clone())?;
self.validate_required_ids(required_ids)?;
let id = self.sos1_constraint_collection.unused_id();
self.sos1_constraint_collection
.insert_with(id, constraint, metadata);
Ok(id)
}
pub fn add_decision_variable(
&mut self,
variable: crate::DecisionVariable,
metadata: crate::DecisionVariableMetadata,
) -> crate::Result<crate::VariableID> {
let id = variable.id();
if self.decision_variables().contains_key(&id) {
crate::bail!({ ?id }, "Duplicate decision variable ID: {id:?}");
}
if self.parameters().contains_key(&id) {
crate::bail!(
{ ?id },
"Variable id {id:?} collides with an existing parameter id",
);
}
if self.decision_variable_dependency().keys().any(|k| k == id) {
crate::bail!(
{ ?id },
"Variable id {id:?} is currently used as a substitution-dependency key",
);
}
self.decision_variables.insert(id, variable);
self.variable_metadata.insert(id, metadata);
Ok(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
assign, coeff,
constraint::{Constraint, ConstraintID},
linear,
polynomial_base::{Linear, LinearMonomial},
DecisionVariable, Function, VariableID,
};
use maplit::btreemap;
#[test]
fn test_insert_constraint_success() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
let objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
let constraint = Constraint::equal_to_zero((linear!(1) + coeff!(2.0)).into());
let result = instance
.insert_constraint(ConstraintID::from(10), constraint.clone())
.unwrap();
assert!(result.is_none());
assert_eq!(instance.constraints().len(), 1);
assert_eq!(
instance.constraints().get(&ConstraintID::from(10)),
Some(&constraint)
);
}
#[test]
fn test_insert_constraint_replace_existing() {
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)),
);
let objective = Function::Linear(Linear::single_term(
LinearMonomial::Variable(VariableID::from(1)),
coeff!(1.0),
));
let mut constraints = BTreeMap::new();
let original_constraint = Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into());
constraints.insert(ConstraintID::from(5), original_constraint.clone());
let mut instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
let new_constraint = Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
let result = instance
.insert_constraint(ConstraintID::from(5), new_constraint.clone())
.unwrap();
assert_eq!(result, Some(original_constraint));
assert_eq!(instance.constraints().len(), 1);
assert_eq!(
instance.constraints().get(&ConstraintID::from(5)),
Some(&new_constraint)
);
}
#[test]
fn test_insert_constraint_undefined_variable() {
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)),
);
let objective = Function::Linear(Linear::single_term(
LinearMonomial::Variable(VariableID::from(1)),
coeff!(1.0),
));
let mut instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(),
)
.unwrap();
let constraint = Constraint::equal_to_zero((linear!(999) + coeff!(1.0)).into());
let result = instance.insert_constraint(ConstraintID::from(1), constraint);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(
err.to_string(),
"Undefined variable ID is used: VariableID(999)"
);
assert_eq!(instance.constraints().len(), 0);
}
#[test]
fn test_insert_constraint_multiple_operations() {
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 objective = Function::Linear(Linear::single_term(
LinearMonomial::Variable(VariableID::from(1)),
coeff!(1.0),
));
let mut instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(),
)
.unwrap();
let constraint1 = Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into());
let constraint2 = Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
let constraint3 = Constraint::equal_to_zero((linear!(3) + coeff!(1.0)).into());
assert!(instance
.insert_constraint(ConstraintID::from(1), constraint1.clone())
.unwrap()
.is_none());
assert!(instance
.insert_constraint(ConstraintID::from(2), constraint2.clone())
.unwrap()
.is_none());
assert!(instance
.insert_constraint(ConstraintID::from(3), constraint3.clone())
.unwrap()
.is_none());
assert_eq!(instance.constraints().len(), 3);
let new_constraint2 = Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into());
let replaced = instance
.insert_constraint(ConstraintID::from(2), new_constraint2.clone())
.unwrap();
assert_eq!(replaced, Some(constraint2));
assert_eq!(instance.constraints().len(), 3);
assert_eq!(
instance.constraints().get(&ConstraintID::from(2)),
Some(&new_constraint2)
);
}
#[test]
fn test_insert_constraint_with_dependency_key() {
let decision_variables = btreemap! {
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 objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
instance.decision_variable_dependency = assign! {
2 <- linear!(1) + coeff!(1.0)
};
let constraint = Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
let result = instance.insert_constraint(ConstraintID::from(1), constraint);
assert_eq!(
result.unwrap_err().to_string(),
"Dependent variable cannot be used in objectives or constraints: VariableID(2)"
);
assert_eq!(instance.constraints().len(), 0);
}
#[test]
fn test_add_constraint_rejects_fixed_variable() {
let mut decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
decision_variables
.get_mut(&VariableID::from(2))
.unwrap()
.substitute(0.0, crate::ATol::default())
.unwrap();
let objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
let bad = crate::Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
let err = instance
.add_constraint(bad, crate::ConstraintMetadata::default())
.unwrap_err();
assert!(
err.to_string().contains("Fixed variable") && err.to_string().contains("VariableID(2)"),
"unexpected error: {err}"
);
assert!(instance.constraints().is_empty());
}
#[test]
fn test_set_objective_with_dependency_key() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
let objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
instance.decision_variable_dependency = assign! {
2 <- linear!(1) + coeff!(1.0)
};
let new_objective = linear!(2) + coeff!(1.0);
let result = instance.set_objective(new_objective.into());
assert_eq!(
result.unwrap_err().to_string(),
"Dependent variable cannot be used in objectives or constraints: VariableID(2)"
);
assert_eq!(instance.objective, Function::from(linear!(1) + coeff!(1.0)));
}
#[test]
fn test_insert_constraint_replace_removed_constraint() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
let objective = (linear!(1) + coeff!(1.0)).into();
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
),
ConstraintID::from(2) => Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into(),
),
};
let mut instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
instance
.relax_constraint(ConstraintID::from(2), "test".to_string(), [])
.unwrap();
assert_eq!(instance.constraints().len(), 1);
assert_eq!(instance.removed_constraints().len(), 1);
let new_constraint =
Constraint::equal_to_zero((linear!(1) + linear!(2) + coeff!(3.0)).into());
let result = instance
.insert_constraint(ConstraintID::from(2), new_constraint.clone())
.unwrap();
assert_eq!(
result,
Some(Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into(),))
);
assert_eq!(instance.constraints().len(), 1);
assert_eq!(instance.removed_constraints().len(), 1);
let (removed, _reason) = instance
.removed_constraints()
.get(&ConstraintID::from(2))
.unwrap();
assert_eq!(removed.equality, new_constraint.equality);
assert_eq!(removed.stage.function, new_constraint.stage.function);
}
#[test]
fn test_insert_constraints_bulk() {
let decision_variables = btreemap! {
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 objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
let constraints = vec![
(
ConstraintID::from(1),
Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into()),
),
(
ConstraintID::from(2),
Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into()),
),
(
ConstraintID::from(3),
Constraint::equal_to_zero((linear!(3) + coeff!(3.0)).into()),
),
];
let replaced = instance.insert_constraints(constraints.clone()).unwrap();
assert!(replaced.is_empty());
assert_eq!(instance.constraints().len(), 3);
for (id, constraint) in &constraints {
assert_eq!(instance.constraints().get(id), Some(constraint));
}
}
#[test]
fn test_insert_constraints_bulk_with_undefined_variable() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
let objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
let constraints = vec![
(
ConstraintID::from(1),
Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into()),
),
(
ConstraintID::from(2),
Constraint::equal_to_zero((linear!(999) + coeff!(2.0)).into()),
),
(
ConstraintID::from(3),
Constraint::equal_to_zero((linear!(2) + coeff!(3.0)).into()),
),
];
let result = instance.insert_constraints(constraints);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Undefined variable ID is used: VariableID(999)"
);
assert_eq!(instance.constraints().len(), 0);
}
#[test]
fn test_insert_constraints_bulk_replace_existing() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
let objective = linear!(1) + coeff!(1.0);
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
),
ConstraintID::from(2) => Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into(),
),
};
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
constraints,
)
.unwrap();
let new_constraints = vec![
(
ConstraintID::from(1),
Constraint::equal_to_zero((linear!(2) + coeff!(10.0)).into()),
),
(
ConstraintID::from(3),
Constraint::equal_to_zero((linear!(1) + coeff!(3.0)).into()),
),
];
let replaced = instance
.insert_constraints(new_constraints.clone())
.unwrap();
assert_eq!(replaced.len(), 1);
assert!(replaced.contains_key(&ConstraintID::from(1)));
assert_eq!(instance.constraints().len(), 3);
}
#[test]
fn test_insert_constraints_bulk_replace_removed() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
};
let objective = linear!(1) + coeff!(1.0);
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
),
};
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
constraints,
)
.unwrap();
instance
.relax_constraint(ConstraintID::from(1), "test".to_string(), [])
.unwrap();
assert_eq!(instance.constraints().len(), 0);
assert_eq!(instance.removed_constraints().len(), 1);
let new_constraints = vec![(
ConstraintID::from(1),
Constraint::equal_to_zero((linear!(2) + coeff!(10.0)).into()),
)];
let replaced = instance.insert_constraints(new_constraints).unwrap();
assert_eq!(replaced.len(), 1);
assert!(replaced.contains_key(&ConstraintID::from(1)));
assert_eq!(instance.removed_constraints().len(), 1);
}
#[test]
fn test_insert_constraints_bulk_with_dependent_variable() {
let decision_variables = btreemap! {
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 objective = linear!(1) + coeff!(1.0);
let mut instance = Instance::new(
Sense::Minimize,
objective.into(),
decision_variables,
BTreeMap::new(),
)
.unwrap();
instance.decision_variable_dependency = assign! {
2 <- linear!(1) + coeff!(1.0)
};
let constraints = vec![
(
ConstraintID::from(1),
Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into()),
),
(
ConstraintID::from(2),
Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into()),
),
];
let result = instance.insert_constraints(constraints);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Dependent variable cannot be used in objectives or constraints: VariableID(2)"
);
assert_eq!(instance.constraints().len(), 0);
}
#[test]
fn test_next_constraint_id() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
};
let objective = (linear!(1) + coeff!(1.0)).into();
let instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(),
)
.unwrap();
assert_eq!(instance.next_constraint_id(), ConstraintID::from(0));
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
};
let objective = (linear!(1) + coeff!(1.0)).into();
let constraints = btreemap! {
ConstraintID::from(3) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
),
ConstraintID::from(15) => Constraint::equal_to_zero((linear!(1) + coeff!(2.0)).into(),
),
};
let mut instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
instance
.relax_constraint(ConstraintID::from(15), "test".to_string(), [])
.unwrap();
assert_eq!(instance.next_constraint_id(), ConstraintID::from(16));
}
}