use super::*;
use std::ops::Neg;
impl Instance {
pub fn as_minimization_problem(&mut self) -> bool {
if self.sense == Sense::Minimize {
false
} else {
self.sense = Sense::Minimize;
self.objective = std::mem::take(&mut self.objective).neg();
true
}
}
pub fn as_maximization_problem(&mut self) -> bool {
if self.sense == Sense::Maximize {
false
} else {
self.sense = Sense::Maximize;
self.objective = std::mem::take(&mut self.objective).neg();
true
}
}
}
impl From<Instance> for ParametricInstance {
fn from(
Instance {
sense,
objective,
decision_variables,
constraints,
removed_constraints,
decision_variable_dependency,
constraint_hints,
description,
named_functions,
..
}: Instance,
) -> Self {
ParametricInstance {
sense,
objective,
decision_variables,
parameters: BTreeMap::default(),
constraints,
removed_constraints,
decision_variable_dependency,
constraint_hints,
description,
named_functions,
}
}
}
impl ParametricInstance {
pub fn with_parameters(self, parameters: crate::v1::Parameters) -> anyhow::Result<Instance> {
use crate::ATol;
use anyhow::bail;
use std::collections::BTreeSet;
let param_map: BTreeMap<VariableID, f64> = parameters
.entries
.iter()
.map(|(k, v)| (VariableID::from(*k), *v))
.collect();
let required_ids: BTreeSet<VariableID> = self.parameters.keys().cloned().collect();
let given_ids: BTreeSet<VariableID> = param_map.keys().cloned().collect();
if !required_ids.is_subset(&given_ids) {
let missing_ids: Vec<_> = required_ids.difference(&given_ids).collect();
for id in &missing_ids {
if let Some(param) = self.parameters.get(id) {
log::error!("Missing parameter: {param:?}");
}
}
bail!(
"Missing parameters: Required IDs {:?}, got {:?}",
required_ids,
given_ids
);
}
let state = crate::v1::State {
entries: parameters.entries.clone(),
};
let atol = ATol::default();
let mut objective = self.objective;
objective.partial_evaluate(&state, atol)?;
let mut constraints = self.constraints;
for (_, constraint) in constraints.iter_mut() {
constraint.function.partial_evaluate(&state, atol)?;
}
let mut removed_constraints = self.removed_constraints;
for (_, removed_constraint) in removed_constraints.iter_mut() {
removed_constraint.partial_evaluate(&state, atol)?;
}
let mut named_functions = self.named_functions;
for (_, named_function) in named_functions.iter_mut() {
named_function.partial_evaluate(&state, atol)?;
}
let mut decision_variable_dependency = self.decision_variable_dependency;
decision_variable_dependency.partial_evaluate(&state, atol)?;
Ok(Instance {
sense: self.sense,
objective,
decision_variables: self.decision_variables,
constraints,
named_functions,
removed_constraints,
decision_variable_dependency,
constraint_hints: self.constraint_hints,
parameters: Some(parameters),
description: self.description,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{constraint::Equality, linear, Function};
use maplit::btreemap;
#[test]
fn decision_variable_dependency_rhs_is_substituted() {
use crate::AcyclicAssignments;
let x = VariableID::from(1);
let dep = VariableID::from(2);
let p = VariableID::from(100);
let assignments =
AcyclicAssignments::new(vec![(dep, Function::from(linear!(1) + linear!(100)))])
.unwrap();
let parametric = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(btreemap! {
x => DecisionVariable::binary(x),
dep => DecisionVariable::binary(dep),
})
.parameters(btreemap! {
p => crate::v1::Parameter { id: 100, ..Default::default() },
})
.constraints(BTreeMap::new())
.decision_variable_dependency(assignments)
.build()
.unwrap();
let params = crate::v1::Parameters {
entries: std::collections::HashMap::from([(100, 1.0)]),
};
let instance = parametric.with_parameters(params).unwrap();
let dep_rhs = instance
.decision_variable_dependency()
.get(&dep)
.expect("dependency entry survives materialization");
let rhs_required: VariableIDSet = dep_rhs.required_ids();
assert!(
!rhs_required.contains(&p),
"parameter id {p:?} survived in dependency RHS: {rhs_required:?}",
);
assert!(
rhs_required.contains(&x),
"decision variable id {x:?} should remain in dependency RHS: {rhs_required:?}",
);
}
#[test]
fn removed_constraint_parameters_are_substituted() {
let x = VariableID::from(1);
let p = VariableID::from(100);
let constraint_id = ConstraintID::from(1);
let removed_constraint = RemovedConstraint {
constraint: Constraint {
id: constraint_id,
function: Function::from(linear!(1) + linear!(100)),
equality: Equality::LessThanOrEqualToZero,
name: None,
subscripts: Vec::new(),
parameters: Default::default(),
description: None,
},
removed_reason: "test".to_string(),
removed_reason_parameters: Default::default(),
};
let parametric = ParametricInstance::builder()
.sense(Sense::Minimize)
.objective(Function::Zero)
.decision_variables(btreemap! {
x => DecisionVariable::binary(x),
})
.parameters(btreemap! {
p => crate::v1::Parameter { id: 100, ..Default::default() },
})
.constraints(BTreeMap::new())
.removed_constraints(btreemap! {
constraint_id => removed_constraint,
})
.build()
.unwrap();
let params = crate::v1::Parameters {
entries: std::collections::HashMap::from([(100, 2.0)]),
};
let instance = parametric.with_parameters(params).unwrap();
let removed = instance
.removed_constraints()
.get(&constraint_id)
.expect("removed constraint survives materialization");
let required_ids = removed.required_ids();
assert!(
!required_ids.contains(&p),
"parameter id {p:?} survived in removed constraint: {required_ids:?}",
);
assert!(
required_ids.contains(&x),
"decision variable id {x:?} should remain in removed constraint: {required_ids:?}",
);
}
}