Skip to content

Commit 8d93be6

Browse files
committed
SolveStats changed to match standard statistics from MiniZinc
1 parent dc4e0e3 commit 8d93be6

File tree

10 files changed

+457
-92
lines changed

10 files changed

+457
-92
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.14.3] - 2025-10-17
6+
- Improved Solver statistics to match standard statistics in MiniZinc
7+
58
## [0.14.2] - 2025-10-17
69
- Fix Variable-to-variable equality pattern
710

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "selen"
3-
version = "0.14.2"
3+
version = "0.14.3"
44
edition = "2024"
55
description = "Constraint Satisfaction Problem (CSP) solver"
66
rust-version = "1.88"

examples/mixed.rs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,51 @@ fn main() {
3232
println!("wn = {:?}", sol[wn]);
3333
println!("hn = {:?}", sol[hn]);
3434
println!("Total (wn + hn) = {:?}", sol[objective]);
35-
println!("\nSolver Statistics:");
36-
println!("==================");
35+
36+
// Extract objective value from solution
37+
let obj_value = match sol[objective] {
38+
Val::ValI(v) => v as f64,
39+
Val::ValF(v) => v,
40+
};
41+
42+
println!("\n========== Solver Statistics ==========\n");
3743
let stats = sol.stats();
38-
println!("Propagation count: {}", stats.propagation_count);
39-
println!("Node count: {}", stats.node_count);
40-
println!("Solve time: {:?}", stats.solve_time);
41-
println!("Variable count: {}", stats.variable_count);
42-
println!("Constraint count: {}", stats.constraint_count);
44+
45+
println!("Propagations: {}", stats.propagation_count);
46+
println!("Nodes: {}", stats.node_count);
47+
println!("Objective: {}", obj_value);
48+
println!("Objective bound: {}", stats.objective_bound);
49+
50+
println!("Total variables: {}", stats.variables);
51+
println!("Integer variables: {}", stats.int_variables);
52+
println!("Boolean variables: {}", stats.bool_variables);
53+
println!("Float variables: {}", stats.float_variables);
54+
55+
println!("Constraints: {}", stats.constraint_count);
56+
println!("Propagators: {}", stats.propagators);
57+
58+
println!("Solve time (ms): {:.3}", stats.solve_time.as_secs_f64() * 1000.0);
59+
println!("Init time (ms): {:.3}", stats.init_time.as_secs_f64() * 1000.0);
4360
println!("Peak memory (MB): {}", stats.peak_memory_mb);
61+
4462
println!("LP solver used: {}", stats.lp_solver_used);
45-
println!("LP constraint count: {}", stats.lp_constraint_count);
63+
println!("LP constraints: {}", stats.lp_constraint_count);
64+
println!("LP variables: {}", stats.lp_variable_count);
65+
4666
if let Some(ref lp_stats) = stats.lp_stats {
47-
println!("\nLP Solver Statistics:");
48-
println!(" Total solve time: {:.2}ms", lp_stats.solve_time_ms);
49-
println!(" Phase I time: {:.2}ms", lp_stats.phase1_time_ms);
50-
println!(" Phase II time: {:.2}ms", lp_stats.phase2_time_ms);
51-
println!(" Phase I iterations: {}", lp_stats.phase1_iterations);
52-
println!(" Phase II iterations: {}", lp_stats.phase2_iterations);
53-
println!(" Variables: {}", lp_stats.n_variables);
54-
println!(" Constraints: {}", lp_stats.n_constraints);
67+
println!("LP solve time (ms): {:.2}", lp_stats.solve_time_ms);
68+
println!("LP phase1 time (ms): {:.2}", lp_stats.phase1_time_ms);
69+
println!("LP phase2 time (ms): {:.2}", lp_stats.phase2_time_ms);
70+
println!("LP phase1 iterations: {}", lp_stats.phase1_iterations);
71+
println!("LP phase2 iterations: {}", lp_stats.phase2_iterations);
72+
println!("LP factorizations: {}", lp_stats.factorizations);
73+
println!("LP n_variables: {}", lp_stats.n_variables);
74+
println!("LP n_constraints: {}", lp_stats.n_constraints);
75+
println!("LP peak memory (MB): {:.2}", lp_stats.peak_memory_mb);
76+
println!("LP phase1 needed: {}", lp_stats.phase1_needed);
5577
}
78+
79+
println!("\n======================================\n");
5680
}
5781
Err(e) => {
5882
println!("No solution found: {:?}", e);

src/core/solution.rs

Lines changed: 167 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
//! println!("Solve time: {:.3}ms", stats.solve_time.as_secs_f64() * 1000.0);
4343
//! println!("Peak memory usage: {}MB", stats.peak_memory_mb);
4444
//! println!("Problem size: {} variables, {} constraints",
45-
//! stats.variable_count, stats.constraint_count);
45+
//! stats.variables, stats.constraint_count);
4646
//!
4747
//! // Use convenience analysis methods
4848
//! println!("Search efficiency: {:.1} propagations/node", stats.efficiency());
@@ -83,7 +83,7 @@
8383
//! println!(" Peak memory: {}MB", stats.peak_memory_mb);
8484
//!
8585
//! println!("Problem characteristics:");
86-
//! println!(" Variables: {}", stats.variable_count);
86+
//! println!(" Variables: {}", stats.variables);
8787
//! println!(" Constraints: {}", stats.constraint_count);
8888
//!
8989
//! // Use all convenience analysis methods
@@ -141,28 +141,67 @@ impl std::error::Error for ValueAccessError {}
141141
/// Statistics collected during the solving process.
142142
#[derive(Clone, Debug, Default, PartialEq)]
143143
pub struct SolveStats {
144+
// ===== Search Metrics =====
144145
/// Number of propagation steps performed during solving
145146
pub propagation_count: usize,
146147
/// Number of search nodes (branching points) explored during solving
147148
pub node_count: usize,
148-
/// Total time spent solving
149+
150+
// ===== Timing =====
151+
/// Total time spent solving (in seconds)
149152
pub solve_time: Duration,
150-
/// Number of variables in the problem
151-
pub variable_count: usize,
153+
/// Initialisation time (in seconds)
154+
pub init_time: Duration,
155+
156+
// ===== Memory =====
157+
/// Peak memory usage during solving (in MB)
158+
///
159+
/// This is the TOTAL peak memory used, including:
160+
/// - CSP solver memory (search stack, variables, propagators)
161+
/// - LP solver memory (if LP was used)
162+
///
163+
/// For breakdown of LP-specific memory, see lp_stats.peak_memory_mb
164+
pub peak_memory_mb: usize,
165+
166+
// ===== Variable Counts =====
167+
/// Total number of variables
168+
pub variables: usize,
169+
/// Number of integer variables created
170+
pub int_variables: usize,
171+
/// Number of bool variables created
172+
pub bool_variables: usize,
173+
/// Number of float variables created
174+
pub float_variables: usize,
175+
/// Number of set variables created
176+
pub set_variables: usize,
177+
178+
// ===== Constraint Metrics =====
152179
/// Number of constraints in the problem
153180
pub constraint_count: usize,
154-
/// Peak memory usage estimate during solving (in MB)
155-
pub peak_memory_mb: usize,
181+
/// Number of propagators created
182+
pub propagators: usize,
183+
184+
// ===== Objective =====
185+
/// Current objective value
186+
pub objective: f64,
187+
/// Dual bound on the objective value
188+
pub objective_bound: f64,
189+
190+
// ===== LP Solver Integration =====
156191
/// Whether the LP solver was used during solving
157192
pub lp_solver_used: bool,
158193
/// Number of linear constraints extracted for LP solver
159194
pub lp_constraint_count: usize,
195+
/// Number of variables used in LP solver (subset of total variables that appear in linear constraints)
196+
pub lp_variable_count: usize,
160197
/// Statistics from LP solver (if used)
161198
pub lp_stats: Option<crate::lpsolver::types::LpStats>,
162199
}
163200

164201
impl SolveStats {
165-
/// Create new statistics with all fields
202+
/// Create new statistics with core fields (for backward compatibility)
203+
///
204+
/// Other fields are set to default values.
166205
pub fn new(
167206
propagation_count: usize,
168207
node_count: usize,
@@ -175,11 +214,68 @@ impl SolveStats {
175214
propagation_count,
176215
node_count,
177216
solve_time,
178-
variable_count,
217+
variables: variable_count,
179218
constraint_count,
180219
peak_memory_mb,
220+
init_time: Duration::ZERO,
221+
222+
int_variables: 0,
223+
bool_variables: 0,
224+
float_variables: 0,
225+
set_variables: 0,
226+
227+
propagators: 0,
228+
objective: 0.0,
229+
objective_bound: 0.0,
230+
231+
lp_solver_used: false,
232+
lp_constraint_count: 0,
233+
lp_variable_count: 0,
234+
lp_stats: None,
235+
}
236+
}
237+
238+
/// Create new statistics with all fields
239+
pub fn with_all_fields(
240+
propagation_count: usize,
241+
node_count: usize,
242+
solve_time: Duration,
243+
init_time: Duration,
244+
variables: usize,
245+
int_variables: usize,
246+
bool_variables: usize,
247+
float_variables: usize,
248+
set_variables: usize,
249+
constraint_count: usize,
250+
propagators: usize,
251+
peak_memory_mb: usize,
252+
objective: f64,
253+
objective_bound: f64,
254+
) -> Self {
255+
Self {
256+
propagation_count,
257+
node_count,
258+
259+
solve_time,
260+
init_time,
261+
262+
peak_memory_mb,
263+
264+
variables,
265+
int_variables,
266+
bool_variables,
267+
float_variables,
268+
set_variables,
269+
270+
constraint_count,
271+
propagators,
272+
273+
objective,
274+
objective_bound,
275+
181276
lp_solver_used: false,
182277
lp_constraint_count: 0,
278+
lp_variable_count: 0,
183279
lp_stats: None,
184280
}
185281
}
@@ -214,20 +310,71 @@ impl SolveStats {
214310
/// Display a summary of the solving statistics
215311
pub fn display_summary(&self) {
216312
println!("=== Solving Statistics ===");
217-
println!("Time: {:.3}ms", self.solve_time.as_secs_f64() * 1000.0);
218-
println!("Memory: {}MB peak usage", self.peak_memory_mb);
219-
println!("Problem: {} variables, {} constraints", self.variable_count, self.constraint_count);
220-
println!("Search: {} propagations, {} nodes",
221-
self.propagation_count, self.node_count);
222313

223-
if self.node_count > 0 {
224-
println!("Efficiency: {:.1} propagations/node", self.efficiency());
225-
} else {
226-
println!("Efficiency: Pure propagation (no search required)");
314+
// Timing
315+
println!("Timing:");
316+
println!(" Solve time: {:.3}ms", self.solve_time.as_secs_f64() * 1000.0);
317+
println!(" Init time: {:.3}ms", self.init_time.as_secs_f64() * 1000.0);
318+
319+
// Memory
320+
println!("Memory:");
321+
println!(" Total peak usage: {}MB", self.peak_memory_mb);
322+
323+
// LP Solver information (if used)
324+
if self.lp_solver_used {
325+
if let Some(ref lp_stats) = self.lp_stats {
326+
println!(" (includes LP solver: {:.2}MB)", lp_stats.peak_memory_mb);
327+
}
227328
}
228329

229-
if self.propagation_count > 0 {
230-
println!("Performance: {:.2}μs/propagation",
330+
// Problem characteristics
331+
println!("Problem:");
332+
println!(" Total variables: {}", self.variables);
333+
if self.int_variables > 0 {
334+
println!(" - Integer: {}", self.int_variables);
335+
}
336+
if self.bool_variables > 0 {
337+
println!(" - Bool: {}", self.bool_variables);
338+
}
339+
if self.float_variables > 0 {
340+
println!(" - Float: {}", self.float_variables);
341+
}
342+
if self.set_variables > 0 {
343+
println!(" - Set: {}", self.set_variables);
344+
}
345+
println!(" Constraints: {}", self.constraint_count);
346+
println!(" Propagators: {}", self.propagators);
347+
348+
// LP Solver information (if used)
349+
if self.lp_solver_used {
350+
println!("LP Solver:");
351+
println!(" Linear constraints: {}", self.lp_constraint_count);
352+
println!(" Linear variables: {}", self.lp_variable_count);
353+
}
354+
355+
// Search metrics
356+
println!("Search:");
357+
println!(" Nodes: {}", self.node_count);
358+
println!(" Propagations: {}", self.propagation_count);
359+
360+
// Objective (if applicable)
361+
if self.objective != 0.0 || self.objective_bound != 0.0 {
362+
println!("Objective:");
363+
println!(" Current value: {}", self.objective);
364+
println!(" Bound: {}", self.objective_bound);
365+
}
366+
367+
// Efficiency analysis
368+
if self.node_count > 0 {
369+
println!("Efficiency:");
370+
println!(" {:.1} propagations/node", self.efficiency());
371+
println!(" {:.2}μs/propagation",
372+
self.time_per_propagation().as_nanos() as f64 / 1000.0);
373+
println!(" {:.2}μs/node",
374+
self.time_per_node().as_nanos() as f64 / 1000.0);
375+
} else if self.propagation_count > 0 {
376+
println!("Efficiency: Pure propagation (no search required)");
377+
println!(" {:.2}μs/propagation",
231378
self.time_per_propagation().as_nanos() as f64 / 1000.0);
232379
}
233380
println!("==========================");

0 commit comments

Comments
 (0)