use crate::{
v1::{Function, Instance, Parameters, ParametricInstance, State},
Evaluate, VariableIDSet,
};
use anyhow::{bail, Result};
use std::{borrow::Cow, collections::BTreeSet};
impl From<Instance> for ParametricInstance {
fn from(
Instance {
description,
objective,
constraints,
decision_variables,
sense,
constraint_hints,
removed_constraints,
parameters: _, decision_variable_dependency,
named_functions,
format_version,
}: Instance,
) -> Self {
Self {
description,
objective,
constraints,
decision_variables,
sense,
parameters: Default::default(),
constraint_hints,
removed_constraints,
decision_variable_dependency,
named_functions,
format_version,
}
}
}
impl From<State> for Parameters {
fn from(State { entries }: State) -> Self {
Self { entries }
}
}
impl From<Parameters> for State {
fn from(Parameters { entries }: Parameters) -> Self {
Self { entries }
}
}
impl ParametricInstance {
pub fn with_parameters(
mut self,
parameters: Parameters,
atol: crate::ATol,
) -> Result<Instance> {
let required_ids: BTreeSet<u64> = self.parameters.iter().map(|p| p.id).collect();
let given_ids: BTreeSet<u64> = parameters.entries.keys().cloned().collect();
if !required_ids.is_subset(&given_ids) {
for ids in required_ids.difference(&given_ids) {
let parameter = self.parameters.iter().find(|p| p.id == *ids).unwrap();
log::error!("Missing parameter: {parameter:?}");
}
bail!(
"Missing parameters: Required IDs {:?}, got {:?}",
required_ids,
given_ids
);
}
let state = State::from(parameters.clone());
if let Some(f) = self.objective.as_mut() {
f.partial_evaluate(&state, atol)?;
}
for constraint in self.constraints.iter_mut() {
constraint.partial_evaluate(&state, atol)?;
}
Ok(Instance {
description: self.description,
objective: self.objective,
constraints: self.constraints,
decision_variables: self.decision_variables,
sense: self.sense,
parameters: Some(parameters),
constraint_hints: self.constraint_hints,
removed_constraints: self.removed_constraints,
decision_variable_dependency: self.decision_variable_dependency,
named_functions: self.named_functions,
format_version: self.format_version,
})
}
pub fn objective(&self) -> Cow<'_, Function> {
match &self.objective {
Some(f) => Cow::Borrowed(f),
None => Cow::Owned(Function::default()),
}
}
pub fn used_ids(&self) -> Result<VariableIDSet> {
let mut used_ids = self.objective().required_ids();
for c in &self.constraints {
used_ids.extend(c.function().required_ids());
}
Ok(used_ids)
}
pub fn defined_decision_variable_ids(&self) -> VariableIDSet {
self.decision_variables
.iter()
.map(|dv| dv.id.into())
.collect()
}
pub fn defined_parameter_ids(&self) -> VariableIDSet {
self.parameters.iter().map(|p| p.id.into()).collect()
}
pub fn validate(&self) -> Result<()> {
self.validate_ids()?;
self.validate_constraint_ids()?;
Ok(())
}
pub fn validate_ids(&self) -> Result<()> {
let mut ids = VariableIDSet::default();
for dv in &self.decision_variables {
if !ids.insert(dv.id.into()) {
bail!("Duplicate decision variable ID: {}", dv.id);
}
}
for p in &self.parameters {
if !ids.insert(p.id.into()) {
bail!("Duplicate parameter ID: {}", p.id);
}
}
let used_ids = self.used_ids()?;
if !used_ids.is_subset(&ids) {
let sub = used_ids.difference(&ids).collect::<BTreeSet<_>>();
bail!("Undefined ID is used: {:?}", sub);
}
Ok(())
}
pub fn validate_constraint_ids(&self) -> Result<()> {
let mut ids = BTreeSet::new();
for c in &self.constraints {
if !ids.insert(c.id) {
bail!("Duplicate constraint ID: {}", c.id);
}
}
for c in &self.removed_constraints {
if let Some(c) = c.constraint.as_ref() {
if !ids.insert(c.id) {
bail!("Duplicate removed constraint ID: {}", c.id);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::abs_diff_eq;
use proptest::prelude::*;
proptest! {
#[test]
fn test_parametric_instance_conversion(instance in Instance::arbitrary()) {
let parametric_instance: ParametricInstance = instance.clone().into();
let converted_instance: Instance = parametric_instance.with_parameters(Parameters::default(), crate::ATol::default()).unwrap();
prop_assert_eq!(&converted_instance.parameters, &Some(Parameters::default()));
prop_assert!(
abs_diff_eq!(instance, converted_instance),
"\nLeft : {:?}\nRight: {:?}", instance, converted_instance
);
}
#[test]
fn validate(pi in ParametricInstance::arbitrary()) {
pi.validate().unwrap();
}
}
}