1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub struct VariableStatsByKind {
6 pub binary: usize,
8 pub integer: usize,
10 pub continuous: usize,
12 pub semi_integer: usize,
14 pub semi_continuous: usize,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct VariableStatsByUsage {
21 pub used_in_objective: usize,
23 pub used_in_constraints: usize,
25 pub used: usize,
27 pub fixed: usize,
29 pub dependent: usize,
31 pub irrelevant: usize,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct DecisionVariableStats {
50 pub total: usize,
52 pub by_kind: VariableStatsByKind,
54 pub by_usage: VariableStatsByUsage,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct ConstraintStats {
61 pub total: usize,
63 pub active: usize,
65 pub removed: usize,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct InstanceStats {
76 pub decision_variables: DecisionVariableStats,
78 pub constraints: ConstraintStats,
80}
81
82impl super::Instance {
83 pub fn stats(&self) -> InstanceStats {
101 let analysis = self.analyze_decision_variables();
102
103 let by_kind = VariableStatsByKind {
104 binary: analysis.binary().len(),
105 integer: analysis.integer().len(),
106 continuous: analysis.continuous().len(),
107 semi_integer: analysis.semi_integer().len(),
108 semi_continuous: analysis.semi_continuous().len(),
109 };
110
111 let by_usage = VariableStatsByUsage {
112 used_in_objective: analysis.used_in_objective().len(),
113 used_in_constraints: analysis
114 .used_in_constraints()
115 .values()
116 .flat_map(|vars| vars.iter())
117 .collect::<std::collections::HashSet<_>>()
118 .len(),
119 used: analysis.used().len(),
120 fixed: analysis.fixed().len(),
121 dependent: analysis.dependent().len(),
122 irrelevant: analysis.irrelevant().len(),
123 };
124
125 let decision_variables = DecisionVariableStats {
126 total: self.decision_variables.len(),
127 by_kind,
128 by_usage,
129 };
130
131 let constraints = ConstraintStats {
132 total: self.constraints.len() + self.removed_constraints.len(),
133 active: self.constraints.len(),
134 removed: self.removed_constraints.len(),
135 };
136
137 InstanceStats {
138 decision_variables,
139 constraints,
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::{
148 coeff, linear, Constraint, ConstraintID, DecisionVariable, Instance, Sense, VariableID,
149 };
150 use maplit::btreemap;
151 use std::collections::BTreeMap;
152
153 #[test]
154 fn test_empty_instance_stats() {
155 let instance = Instance::default();
156 let stats = instance.stats();
157
158 assert_eq!(stats.decision_variables.total, 0);
159 assert_eq!(stats.decision_variables.by_kind.binary, 0);
160 assert_eq!(stats.decision_variables.by_kind.integer, 0);
161 assert_eq!(stats.decision_variables.by_kind.continuous, 0);
162 assert_eq!(stats.decision_variables.by_usage.used, 0);
163 assert_eq!(stats.decision_variables.by_usage.fixed, 0);
164 assert_eq!(stats.decision_variables.by_usage.dependent, 0);
165 assert_eq!(stats.decision_variables.by_usage.irrelevant, 0);
166
167 assert_eq!(stats.constraints.total, 0);
168 assert_eq!(stats.constraints.active, 0);
169 assert_eq!(stats.constraints.removed, 0);
170 }
171
172 #[test]
173 fn test_instance_with_variables_stats() {
174 let decision_variables = btreemap! {
175 VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
176 VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
177 VariableID::from(3) => DecisionVariable::integer(VariableID::from(3)),
178 VariableID::from(4) => DecisionVariable::continuous(VariableID::from(4)),
179 };
180
181 let objective = (linear!(1) + linear!(2)).into();
183
184 let instance = Instance::new(
185 Sense::Minimize,
186 objective,
187 decision_variables,
188 BTreeMap::new(),
189 )
190 .unwrap();
191
192 let stats = instance.stats();
193
194 assert_eq!(stats.decision_variables.total, 4);
195 assert_eq!(stats.decision_variables.by_kind.binary, 2);
196 assert_eq!(stats.decision_variables.by_kind.integer, 1);
197 assert_eq!(stats.decision_variables.by_kind.continuous, 1);
198 assert_eq!(stats.decision_variables.by_usage.used_in_objective, 2);
199 assert_eq!(stats.decision_variables.by_usage.used, 2);
200 assert_eq!(stats.decision_variables.by_usage.irrelevant, 2); }
202
203 #[test]
204 fn test_instance_with_constraints_stats() {
205 let decision_variables = btreemap! {
206 VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
207 VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
208 VariableID::from(3) => DecisionVariable::binary(VariableID::from(3)),
209 };
210
211 let objective = (linear!(1) + coeff!(1.0)).into();
212
213 let constraints = btreemap! {
214 ConstraintID::from(1) => Constraint::equal_to_zero(
215 ConstraintID::from(1),
216 (linear!(1) + linear!(2) + coeff!(-1.0)).into(),
217 ),
218 ConstraintID::from(2) => Constraint::equal_to_zero(
219 ConstraintID::from(2),
220 (linear!(3) + coeff!(-1.0)).into(),
221 ),
222 };
223
224 let mut instance =
225 Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
226
227 instance
229 .relax_constraint(ConstraintID::from(2), "Test removal".to_string(), [])
230 .unwrap();
231
232 let stats = instance.stats();
233
234 assert_eq!(stats.constraints.total, 2);
235 assert_eq!(stats.constraints.active, 1);
236 assert_eq!(stats.constraints.removed, 1);
237 assert_eq!(stats.decision_variables.by_usage.used_in_constraints, 2);
240 assert_eq!(stats.decision_variables.by_usage.used, 2);
242 }
243
244 #[test]
245 fn test_stats_serialization() {
246 let decision_variables = btreemap! {
247 VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
248 };
249
250 let objective = (linear!(1) + coeff!(1.0)).into();
251
252 let instance = Instance::new(
253 Sense::Minimize,
254 objective,
255 decision_variables,
256 BTreeMap::new(),
257 )
258 .unwrap();
259
260 let stats = instance.stats();
261
262 let json = serde_json::to_string(&stats).unwrap();
264 let deserialized: InstanceStats = serde_json::from_str(&json).unwrap();
265
266 assert_eq!(stats, deserialized);
267 }
268
269 #[test]
270 fn test_stats_snapshot_empty() {
271 let instance = Instance::default();
272 let stats = instance.stats();
273 insta::assert_yaml_snapshot!(stats);
274 }
275
276 #[test]
277 fn test_stats_snapshot_with_variables() {
278 let decision_variables = btreemap! {
279 VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
280 VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
281 VariableID::from(3) => DecisionVariable::integer(VariableID::from(3)),
282 VariableID::from(4) => DecisionVariable::continuous(VariableID::from(4)),
283 VariableID::from(5) => DecisionVariable::semi_integer(VariableID::from(5)),
284 };
285
286 let objective = (linear!(1) + linear!(2)).into();
287
288 let instance = Instance::new(
289 Sense::Minimize,
290 objective,
291 decision_variables,
292 BTreeMap::new(),
293 )
294 .unwrap();
295
296 let stats = instance.stats();
297 insta::assert_yaml_snapshot!(stats);
298 }
299
300 #[test]
301 fn test_stats_snapshot_with_constraints() {
302 let decision_variables = btreemap! {
303 VariableID::from(1) => DecisionVariable::binary(VariableID::from(1)),
304 VariableID::from(2) => DecisionVariable::binary(VariableID::from(2)),
305 VariableID::from(3) => DecisionVariable::integer(VariableID::from(3)),
306 };
307
308 let objective = (linear!(1) + coeff!(1.0)).into();
309
310 let constraints = btreemap! {
311 ConstraintID::from(1) => Constraint::equal_to_zero(
312 ConstraintID::from(1),
313 (linear!(1) + linear!(2) + coeff!(-1.0)).into(),
314 ),
315 ConstraintID::from(2) => Constraint::equal_to_zero(
316 ConstraintID::from(2),
317 (linear!(2) + linear!(3) + coeff!(-5.0)).into(),
318 ),
319 ConstraintID::from(3) => Constraint::equal_to_zero(
320 ConstraintID::from(3),
321 (linear!(3) + coeff!(-10.0)).into(),
322 ),
323 };
324
325 let mut instance =
326 Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
327
328 instance
330 .relax_constraint(ConstraintID::from(3), "Not needed".to_string(), [])
331 .unwrap();
332
333 let stats = instance.stats();
334 insta::assert_yaml_snapshot!(stats);
335 }
336}