use super::*;
use crate::{ATol, Evaluate, VariableIDSet};
use anyhow::{anyhow, Result};
use fnv::FnvHashMap;
use std::collections::BTreeMap;
impl Evaluate for Instance {
type Output = crate::Solution;
type SampledOutput = crate::SampleSet;
fn evaluate(&self, state: &v1::State, atol: ATol) -> Result<Self::Output> {
let state = self
.analyze_decision_variables()
.populate(state.clone(), atol)?;
let objective = self.objective.evaluate(&state, atol)?;
let mut evaluated_constraints = BTreeMap::default();
for constraint in self.constraints.values() {
let evaluated = constraint.evaluate(&state, atol)?;
evaluated_constraints.insert(*evaluated.id(), evaluated);
}
for constraint in self.removed_constraints.values() {
let evaluated = constraint.evaluate(&state, atol)?;
evaluated_constraints.insert(*evaluated.id(), evaluated);
}
let mut decision_variables = BTreeMap::default();
for dv in self.decision_variables.values() {
let evaluated_dv = dv.evaluate(&state, atol)?;
decision_variables.insert(*evaluated_dv.id(), evaluated_dv);
}
let mut evaluated_named_functions = BTreeMap::default();
for (id, named_function) in self.named_functions.iter() {
let evaluated_named_function = named_function.evaluate(&state, atol)?;
evaluated_named_functions.insert(*id, evaluated_named_function);
}
let sense = self.sense();
let solution = unsafe {
crate::Solution::builder()
.objective(objective)
.evaluated_constraints(evaluated_constraints)
.evaluated_named_functions(evaluated_named_functions)
.decision_variables(decision_variables)
.sense(sense)
.build_unchecked()?
};
Ok(solution)
}
fn evaluate_samples(&self, samples: &v1::Samples, atol: ATol) -> Result<Self::SampledOutput> {
let samples = {
let analysis = self.analyze_decision_variables();
let mut samples = samples.clone();
for sample in samples.states_mut() {
let sample = sample?;
let state = std::mem::take(sample);
let state = analysis.populate(state, atol)?;
*sample = state;
}
samples
};
let mut feasible_relaxed: FnvHashMap<u64, bool> =
samples.ids().map(|id| (*id, true)).collect();
let mut constraints = Vec::new();
for c in self.constraints.values() {
let evaluated = c.evaluate_samples(&samples, atol)?;
for sample_id in evaluated.infeasible_ids(atol) {
feasible_relaxed.insert(sample_id.into_inner(), false);
}
constraints.push(evaluated);
}
let mut feasible = feasible_relaxed.clone();
for c in self.removed_constraints.values() {
let v = c.evaluate_samples(&samples, atol)?;
for sample_id in v.infeasible_ids(atol) {
feasible.insert(sample_id.into_inner(), false);
}
constraints.push(v);
}
let objectives = self.objective().evaluate_samples(&samples, atol)?;
let mut decision_variables = std::collections::BTreeMap::new();
for dv in self.decision_variables.values() {
let sampled_dv = dv.evaluate_samples(&samples, atol)?;
decision_variables.insert(dv.id(), sampled_dv);
}
let mut constraints_map = std::collections::BTreeMap::new();
for constraint in constraints {
constraints_map.insert(*constraint.id(), constraint);
}
let mut named_functions = std::collections::BTreeMap::new();
for (id, named_function) in self.named_functions.iter() {
let sampled_named_function = named_function.evaluate_samples(&samples, atol)?;
named_functions.insert(*id, sampled_named_function);
}
Ok(crate::SampleSet::builder()
.decision_variables(decision_variables)
.objectives(objectives.try_into()?)
.constraints(constraints_map)
.named_functions(named_functions)
.sense(self.sense)
.build()?)
}
fn partial_evaluate(&mut self, state: &v1::State, atol: ATol) -> Result<()> {
let updated_state = self
.constraint_hints
.partial_evaluate(state.clone(), atol)?;
for (id, value) in updated_state.entries.iter() {
let Some(dv) = self.decision_variables.get_mut(&VariableID::from(*id)) else {
return Err(anyhow!("Unknown decision variable (ID={id}) in state."));
};
dv.substitute(*value, atol)?;
}
self.objective.partial_evaluate(&updated_state, atol)?;
for constraint in self.constraints.values_mut() {
constraint.partial_evaluate(&updated_state, atol)?;
}
for named_function in self.named_functions.values_mut() {
named_function.partial_evaluate(&updated_state, atol)?;
}
self.decision_variable_dependency
.partial_evaluate(&updated_state, atol)?;
Ok(())
}
fn required_ids(&self) -> VariableIDSet {
self.analyze_decision_variables().used().clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::random::arbitrary_split_state;
use crate::{coeff, constraint_hints::OneHot, linear};
use ::approx::AbsDiffEq;
use proptest::prelude::*;
use std::collections::HashMap;
proptest! {
#[test]
fn test_evaluate_instance(
(instance, state) in Instance::arbitrary()
.prop_flat_map(|instance| {
let state = instance.arbitrary_state();
(Just(instance), state)
})
) {
let analysis = instance.analyze_decision_variables();
let solution = instance.evaluate(&state, ATol::default()).unwrap();
let ids: VariableIDSet = solution.state().entries.keys().map(|id| VariableID::from(*id)).collect();
prop_assert_eq!(&ids, analysis.all());
}
#[test]
fn partial_evaluate(
(mut instance, state, (u, v)) in Instance::arbitrary()
.prop_flat_map(|instance| {
let state = instance.arbitrary_state();
(Just(instance), state).prop_flat_map(|(instance, state)| {
let split = arbitrary_split_state(&state);
(Just(instance), Just(state), split)
})
})
) {
let s1 = instance.evaluate(&state, ATol::default()).unwrap();
instance.partial_evaluate(&u, ATol::default()).unwrap();
let s2 = instance.evaluate(&v, ATol::default()).unwrap();
prop_assert!(s1.state().abs_diff_eq(&s2.state(), ATol::default()));
}
}
#[test]
fn test_partial_evaluate_with_constraint_hints() {
use crate::DecisionVariable;
use maplit::btreemap;
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 = Function::from(linear!(1) + linear!(2) + linear!(3));
let mut constraint_hints = crate::constraint_hints::ConstraintHints::default();
constraint_hints.one_hot_constraints.push(OneHot {
id: ConstraintID::from(100),
variables: vec![
VariableID::from(1),
VariableID::from(2),
VariableID::from(3),
]
.into_iter()
.collect(),
});
let mut instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(), )
.unwrap();
instance.constraint_hints = constraint_hints;
let initial_state = v1::State::from(HashMap::from([(2, 1.0)]));
instance
.partial_evaluate(&initial_state, ATol::default())
.unwrap();
let empty_state = v1::State::default();
let solution = instance.evaluate(&empty_state, ATol::default()).unwrap();
assert_eq!(solution.state().entries.get(&1), Some(&0.0));
assert_eq!(solution.state().entries.get(&2), Some(&1.0));
assert_eq!(solution.state().entries.get(&3), Some(&0.0));
assert_eq!(*solution.objective(), 1.0);
}
#[test]
fn test_evaluate_named_function_with_fixed_dependent_irrelevant_variables() {
use crate::{DecisionVariable, NamedFunction, NamedFunctionID};
use maplit::btreemap;
let x1 = DecisionVariable::continuous(VariableID::from(1));
let mut x2 = DecisionVariable::continuous(VariableID::from(2));
x2.substitute(3.0, ATol::default()).unwrap(); let x3 = DecisionVariable::continuous(VariableID::from(3));
let x4 = DecisionVariable::continuous(VariableID::from(4));
let x5 = DecisionVariable::continuous(VariableID::from(5));
let decision_variables = btreemap! {
VariableID::from(1) => x1,
VariableID::from(2) => x2,
VariableID::from(3) => x3,
VariableID::from(4) => x4,
VariableID::from(5) => x5,
};
let objective = Function::from(linear!(1));
let decision_variable_dependency = crate::AcyclicAssignments::new(vec![(
VariableID::from(3),
Function::from(coeff!(2.0) * linear!(4)),
)])
.unwrap();
let named_function = NamedFunction {
id: NamedFunctionID::from(1),
function: Function::from(linear!(2) + linear!(3) + linear!(4) + linear!(5)),
name: Some("f".to_string()),
subscripts: vec![],
parameters: Default::default(),
description: None,
};
let named_functions = btreemap! {
NamedFunctionID::from(1) => named_function,
};
let mut instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(), )
.unwrap();
instance.decision_variable_dependency = decision_variable_dependency;
instance.named_functions = named_functions;
let analysis = instance.analyze_decision_variables();
assert!(analysis.used().contains(&VariableID::from(1)));
assert!(analysis.fixed().contains_key(&VariableID::from(2)));
assert!(analysis.dependent().contains_key(&VariableID::from(3)));
assert!(analysis.irrelevant().contains_key(&VariableID::from(4)));
assert!(analysis.irrelevant().contains_key(&VariableID::from(5)));
let state = v1::State::from(HashMap::from([(1, 1.0), (4, 2.0), (5, 10.0)]));
let solution = instance.evaluate(&state, ATol::default()).unwrap();
assert_eq!(*solution.objective(), 1.0);
let evaluated_nf = solution
.evaluated_named_functions()
.get(&NamedFunctionID::from(1))
.unwrap();
assert_eq!(evaluated_nf.evaluated_value(), 19.0);
let used_ids = evaluated_nf.used_decision_variable_ids();
assert!(used_ids.contains(&VariableID::from(2)));
assert!(used_ids.contains(&VariableID::from(3)));
assert!(used_ids.contains(&VariableID::from(4)));
assert!(used_ids.contains(&VariableID::from(5)));
assert!(!used_ids.contains(&VariableID::from(1)));
}
}