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 analysis = self.analyze_decision_variables();
let by_kind = VariableStatsByKind {
binary: analysis.binary().len(),
integer: analysis.integer().len(),
continuous: analysis.continuous().len(),
semi_integer: analysis.semi_integer().len(),
semi_continuous: analysis.semi_continuous().len(),
};
let by_usage = VariableStatsByUsage {
used_in_objective: analysis.used_in_objective().len(),
used_in_constraints: analysis
.used_in_constraints()
.values()
.flat_map(|vars| vars.iter())
.collect::<std::collections::HashSet<_>>()
.len(),
used: analysis.used().len(),
fixed: analysis.fixed().len(),
dependent: analysis.dependent().len(),
irrelevant: analysis.irrelevant().len(),
};
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(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
VariableID::from(3) => DecisionVariable::integer(VariableID::from(3)),
VariableID::from(4) => DecisionVariable::continuous(VariableID::from(4)),
};
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(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
VariableID::from(3) => DecisionVariable::binary(VariableID::from(3)),
};
let objective = (linear!(1) + coeff!(1.0)).into();
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero(
ConstraintID::from(1),
(linear!(1) + linear!(2) + coeff!(-1.0)).into(),
),
ConstraintID::from(2) => Constraint::equal_to_zero(
ConstraintID::from(2),
(linear!(3) + coeff!(-1.0)).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(VariableID::from(1)),
};
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(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
VariableID::from(3) => DecisionVariable::integer(VariableID::from(3)),
VariableID::from(4) => DecisionVariable::continuous(VariableID::from(4)),
VariableID::from(5) => DecisionVariable::semi_integer(VariableID::from(5)),
};
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(1)),
VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
VariableID::from(3) => DecisionVariable::integer(VariableID::from(3)),
};
let objective = (linear!(1) + coeff!(1.0)).into();
let constraints = btreemap! {
ConstraintID::from(1) => Constraint::equal_to_zero(
ConstraintID::from(1),
(linear!(1) + linear!(2) + coeff!(-1.0)).into(),
),
ConstraintID::from(2) => Constraint::equal_to_zero(
ConstraintID::from(2),
(linear!(2) + linear!(3) + coeff!(-5.0)).into(),
),
ConstraintID::from(3) => Constraint::equal_to_zero(
ConstraintID::from(3),
(linear!(3) + coeff!(-10.0)).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);
}
}