use super::DecisionVariableRole;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VariableStatsByKind {
pub binary: usize,
pub integer: usize,
pub continuous: usize,
pub semi_integer: usize,
pub semi_continuous: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VariableStatsByUsage {
pub used_in_objective: usize,
pub used_in_constraints: usize,
pub used: usize,
pub fixed: usize,
pub dependent: usize,
pub irrelevant: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecisionVariableStats {
pub total: usize,
pub by_kind: VariableStatsByKind,
pub by_usage: VariableStatsByUsage,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConstraintStats {
pub total: usize,
pub active: usize,
pub removed: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InstanceStats {
pub decision_variables: DecisionVariableStats,
pub constraints: ConstraintStats,
}
impl super::Instance {
pub fn stats(&self) -> InstanceStats {
let by_kind = VariableStatsByKind {
binary: self
.decision_variables()
.values()
.filter(|dv| dv.kind() == crate::Kind::Binary)
.count(),
integer: self
.decision_variables()
.values()
.filter(|dv| dv.kind() == crate::Kind::Integer)
.count(),
continuous: self
.decision_variables()
.values()
.filter(|dv| dv.kind() == crate::Kind::Continuous)
.count(),
semi_integer: self
.decision_variables()
.values()
.filter(|dv| dv.kind() == crate::Kind::SemiInteger)
.count(),
semi_continuous: self
.decision_variables()
.values()
.filter(|dv| dv.kind() == crate::Kind::SemiContinuous)
.count(),
};
let usage = self.decision_variable_usage();
let roles = self.decision_variable_roles();
let by_usage = VariableStatsByUsage {
used_in_objective: usage.used_in_objective().len(),
used_in_constraints: usage
.used_in_constraints()
.values()
.flat_map(|vars| vars.iter())
.collect::<std::collections::HashSet<_>>()
.len(),
used: usage.used().len(),
fixed: roles
.values()
.filter(|&&role| role == DecisionVariableRole::Fixed)
.count(),
dependent: roles
.values()
.filter(|&&role| role == DecisionVariableRole::Dependent)
.count(),
irrelevant: roles
.values()
.filter(|&&role| role == DecisionVariableRole::Irrelevant)
.count(),
};
let decision_variables = DecisionVariableStats {
total: self.decision_variables.len(),
by_kind,
by_usage,
};
let constraints = ConstraintStats {
total: self.constraints().len() + self.removed_constraints().len(),
active: self.constraints().len(),
removed: self.removed_constraints().len(),
};
InstanceStats {
decision_variables,
constraints,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
coeff, linear, Constraint, ConstraintID, DecisionVariable, Instance, Sense, VariableID,
};
use maplit::btreemap;
use std::collections::BTreeMap;
#[test]
fn test_empty_instance_stats() {
let instance = Instance::default();
let stats = instance.stats();
assert_eq!(stats.decision_variables.total, 0);
assert_eq!(stats.decision_variables.by_kind.binary, 0);
assert_eq!(stats.decision_variables.by_kind.integer, 0);
assert_eq!(stats.decision_variables.by_kind.continuous, 0);
assert_eq!(stats.decision_variables.by_usage.used, 0);
assert_eq!(stats.decision_variables.by_usage.fixed, 0);
assert_eq!(stats.decision_variables.by_usage.dependent, 0);
assert_eq!(stats.decision_variables.by_usage.irrelevant, 0);
assert_eq!(stats.constraints.total, 0);
assert_eq!(stats.constraints.active, 0);
assert_eq!(stats.constraints.removed, 0);
}
#[test]
fn test_instance_with_variables_stats() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(),
VariableID::from(2) => DecisionVariable::binary(),
VariableID::from(3) => DecisionVariable::integer(),
VariableID::from(4) => DecisionVariable::continuous(),
};
let objective = (linear!(1) + linear!(2)).into();
let instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(),
)
.unwrap();
let stats = instance.stats();
assert_eq!(stats.decision_variables.total, 4);
assert_eq!(stats.decision_variables.by_kind.binary, 2);
assert_eq!(stats.decision_variables.by_kind.integer, 1);
assert_eq!(stats.decision_variables.by_kind.continuous, 1);
assert_eq!(stats.decision_variables.by_usage.used_in_objective, 2);
assert_eq!(stats.decision_variables.by_usage.used, 2);
assert_eq!(stats.decision_variables.by_usage.irrelevant, 2); }
#[test]
fn test_instance_with_constraints_stats() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(),
VariableID::from(2) => DecisionVariable::binary(),
VariableID::from(3) => DecisionVariable::binary(),
};
let objective = (linear!(1) + coeff!(1.0)).unwrap().into();
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero(((linear!(1) + linear!(2)).unwrap() + coeff!(-1.0)).unwrap().into(),
),
ConstraintID::from(2) => Constraint::equal_to_zero((linear!(3) + coeff!(-1.0)).unwrap().into(),
),
};
let mut instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
instance
.relax_constraint(ConstraintID::from(2), "Test removal".to_string(), [])
.unwrap();
let stats = instance.stats();
assert_eq!(stats.constraints.total, 2);
assert_eq!(stats.constraints.active, 1);
assert_eq!(stats.constraints.removed, 1);
assert_eq!(stats.decision_variables.by_usage.used_in_constraints, 2);
assert_eq!(stats.decision_variables.by_usage.used, 2);
}
#[test]
fn test_stats_serialization() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(),
};
let objective = (linear!(1) + coeff!(1.0)).into();
let instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(),
)
.unwrap();
let stats = instance.stats();
let json = serde_json::to_string(&stats).unwrap();
let deserialized: InstanceStats = serde_json::from_str(&json).unwrap();
assert_eq!(stats, deserialized);
}
#[test]
fn test_stats_snapshot_empty() {
let instance = Instance::default();
let stats = instance.stats();
insta::assert_yaml_snapshot!(stats);
}
#[test]
fn test_stats_snapshot_with_variables() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(),
VariableID::from(2) => DecisionVariable::binary(),
VariableID::from(3) => DecisionVariable::integer(),
VariableID::from(4) => DecisionVariable::continuous(),
VariableID::from(5) => DecisionVariable::semi_integer(),
};
let objective = (linear!(1) + linear!(2)).into();
let instance = Instance::new(
Sense::Minimize,
objective,
decision_variables,
BTreeMap::new(),
)
.unwrap();
let stats = instance.stats();
insta::assert_yaml_snapshot!(stats);
}
#[test]
fn test_stats_snapshot_with_constraints() {
let decision_variables = btreemap! {
VariableID::from(1) => DecisionVariable::binary(),
VariableID::from(2) => DecisionVariable::binary(),
VariableID::from(3) => DecisionVariable::integer(),
};
let objective = (linear!(1) + coeff!(1.0)).unwrap().into();
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero(((linear!(1) + linear!(2)).unwrap() + coeff!(-1.0)).unwrap().into(),
),
ConstraintID::from(2) => Constraint::equal_to_zero(((linear!(2) + linear!(3)).unwrap() + coeff!(-5.0)).unwrap().into(),
),
ConstraintID::from(3) => Constraint::equal_to_zero((linear!(3) + coeff!(-10.0)).unwrap().into(),
),
};
let mut instance =
Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
instance
.relax_constraint(ConstraintID::from(3), "Not needed".to_string(), [])
.unwrap();
let stats = instance.stats();
insta::assert_yaml_snapshot!(stats);
}
}