diff --git a/src/main.rs b/src/main.rs index 240e9ff..6f2076b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,10 @@ struct Args { #[arg(long)] limit: Option, // Optional value + /// Solve n times and averages the results + #[arg(long)] + benchmark: Option, // Optional value + /// Disable randomization of the collapse #[arg(long)] norand: bool, @@ -44,7 +48,11 @@ fn main() { solver.debug_mode(args.debug); solver.random_mode(!args.norand); solver.grid_display_mode(args.grid); - + + if let Some(number) = args.benchmark { + solver.benchmark(number); + return + } if let Err(reason) = solver.solve(args.limit) { println!("{}", reason); if args.debug { diff --git a/src/solver/cell.rs b/src/solver/cell.rs index 12b6694..df3c9d0 100644 --- a/src/solver/cell.rs +++ b/src/solver/cell.rs @@ -57,10 +57,6 @@ impl Cell { } } - pub fn get_state(&self) -> Option { - self.state - } - pub fn get_allowed(&self) -> Vec { self.blocking_states .iter() diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 6fe7802..8cb1ae5 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -1,5 +1,7 @@ use std::collections::HashSet; use std::time::Instant; +use std::time::Duration; +use std::ops::MulAssign; use std::fmt; pub mod sudoku; @@ -14,7 +16,7 @@ pub enum WaveError { impl fmt::Display for WaveError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - WaveError::Contradiction => write!(f, "Error: The puzzle contradicts itself."), + WaveError::Contradiction => write!(f, "Error: There is a contradiction in the puzzle."), WaveError::NoHistory => write!(f, "Error: The puzzle is impossible (backtracked to start)."), } } @@ -33,6 +35,77 @@ impl fmt::Display for Step { } } +pub struct SolveResult { + total_time: Duration, + propagation: (usize, Duration), + collapse: (usize, Duration), + backtrack: (usize, Duration), +} + +impl fmt::Display for SolveResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let propagation_percentage = (self.propagation.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0; + let collapse_percentage = (self.collapse.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0; + let backtrack_percentage = (self.backtrack.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0; + + write!(f, "# total time : {:.2?} ({:.2?}% propagation, {:.2?}% forced collapse, {:.2?}% backtrack)\n- {} propagations ({:.2?}), {} forced collapse ({:.2?}), {} backtrack ({:.2?})", + self.total_time, propagation_percentage, collapse_percentage, backtrack_percentage, + self.propagation.0, (self.propagation.1.checked_div(self.propagation.0 as u32)).unwrap_or(std::time::Duration::ZERO), + self.collapse.0, (self.collapse.1.checked_div(self.collapse.0 as u32)).unwrap_or(std::time::Duration::ZERO), + self.backtrack.0, (self.backtrack.1.checked_div(self.backtrack.0 as u32)).unwrap_or(std::time::Duration::ZERO) + ) + } +} + +impl std::ops::AddAssign for SolveResult { + fn add_assign(&mut self, other: Self) { + self.total_time += other.total_time; + self.propagation.0 += other.propagation.0; + self.propagation.1 += other.propagation.1; + self.collapse.0 += other.collapse.0; + self.collapse.1 += other.collapse.1; + self.backtrack.0 += other.backtrack.0; + self.backtrack.1 += other.backtrack.1; + } +} + +impl MulAssign for SolveResult { + fn mul_assign(&mut self, factor: f32) { + // Scale the durations + self.total_time = scale_duration(self.total_time, factor); + self.propagation.1 = scale_duration(self.propagation.1, factor); + self.collapse.1 = scale_duration(self.collapse.1, factor); + self.backtrack.1 = scale_duration(self.backtrack.1, factor); + + // Scale the usize counts (rounded to nearest integer) + self.propagation.0 = scale_usize(self.propagation.0, factor); + self.collapse.0 = scale_usize(self.collapse.0, factor); + self.backtrack.0 = scale_usize(self.backtrack.0, factor); + } +} + +impl Default for SolveResult { + fn default() -> Self { + Self { + total_time: Duration::ZERO, + propagation: (0, Duration::ZERO), + collapse: (0, Duration::ZERO), + backtrack: (0, Duration::ZERO), + } + } +} + +// Helper to scale `Duration` by `f32` +fn scale_duration(duration: Duration, factor: f32) -> Duration { + let nanos = duration.as_secs_f64() * (factor as f64); + Duration::from_secs_f64(nanos) +} + +// Helper to scale `usize` by `f32` +fn scale_usize(value: usize, factor: f32) -> usize { + ((value as f64) * (factor as f64)).round() as usize +} + pub struct Solver { grid: Vec>, history: Vec, @@ -41,6 +114,7 @@ pub struct Solver { square_size: usize, debug_display: bool, grid_display: bool, + benchmarking: bool, collapse_option: cell::CollapseOption, } @@ -57,10 +131,19 @@ impl Solver { square_size: order, debug_display: false, grid_display: false, + benchmarking: false, collapse_option: cell::CollapseOption::Random, } } + fn reset_grid(&mut self) { + let states = (1..=self.size).collect(); + let empty_grid = vec![vec![cell::Cell::new(states); self.size]; self.size]; + self.grid = empty_grid.clone(); + self.history = vec![]; + self.last_move_index = 0; + } + pub fn random_mode(&mut self, collapse_random: bool) { if collapse_random { self.collapse_option = cell::CollapseOption::Random; @@ -69,14 +152,35 @@ impl Solver { } } - pub fn solve(&mut self, solver_limit: Option) -> Result<(), WaveError> { + pub fn benchmark(&mut self, solve_number: usize) { + self.benchmarking = true; + + let mut results_sum: SolveResult = SolveResult::default(); + let mut error_number: usize = 0; + for _ in 0..solve_number { + println!(); + self.reset_grid(); + match self.solve(None) { + Ok(result) => { + results_sum += result; + } + Err(reason) => { + println!("{}", reason); + error_number += 1; + } + } + } + results_sum *= 1.0 / ((solve_number - error_number) as f32); + println!("\n---------\n{}\n\n{} errors", results_sum, error_number); + } + + pub fn solve(&mut self, solver_limit: Option) -> Result { let start_time = Instant::now(); self.display(ui::DisplayMode::Full); let mut propagation_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO); let mut collapse_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO); let mut backtrack_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO); - println!("# started"); self.debug("--------"); while self.history.len() < self.size * self.size { @@ -129,23 +233,18 @@ impl Solver { } } self.debug("--------"); - let total_elapsed = start_time.elapsed(); - let propagation_percentage = (propagation_counter.1.as_secs_f64() / total_elapsed.as_secs_f64()) * 100.0; - let collapse_percentage = (collapse_counter.1.as_secs_f64() / total_elapsed.as_secs_f64()) * 100.0; - let backtrack_percentage = (backtrack_counter.1.as_secs_f64() / total_elapsed.as_secs_f64()) * 100.0; - - println!("\n# finished in {:.2?} ({:.2?}% propagation, {:.2?}% forced collapse, {:.2?}% backtrack)", - total_elapsed, propagation_percentage, collapse_percentage, backtrack_percentage - ); - println!("- {} propagations ({:.2?}), {} forced collapse ({:.2?}), {} backtrack ({:.2?})", - propagation_counter.0, (propagation_counter.1.checked_div(propagation_counter.0 as u32)).unwrap_or(std::time::Duration::ZERO), - collapse_counter.0, (collapse_counter.1.checked_div(collapse_counter.0 as u32)).unwrap_or(std::time::Duration::ZERO), - backtrack_counter.0, (backtrack_counter.1.checked_div(backtrack_counter.0 as u32)).unwrap_or(std::time::Duration::ZERO) - ); if !self.grid_display || self.debug_display { self.display(ui::DisplayMode::Full); } - Ok(()) + let total_time = start_time.elapsed(); + let result = SolveResult { + total_time, + propagation : propagation_counter, + collapse : collapse_counter, + backtrack : backtrack_counter, + }; + println!("\n{}", result); + Ok(result) } fn collapse(&mut self) -> Result<(), WaveError> { diff --git a/src/solver/ui.rs b/src/solver/ui.rs index 00bf550..aad2d33 100644 --- a/src/solver/ui.rs +++ b/src/solver/ui.rs @@ -21,6 +21,9 @@ pub enum DisplayMode { impl Solver { pub fn display(&self, display_mode: DisplayMode) { + if self.benchmarking { + return + } if let DisplayMode::Erase = display_mode { let height = self.size + self.square_size + 2; print!("\x1b[{}A", height); diff --git a/todo b/todo new file mode 100644 index 0000000..3174cc2 --- /dev/null +++ b/todo @@ -0,0 +1,12 @@ + +benchmark 3 (n = 3000) +# total time : 10.05ms (62.03% propagation, 37.31% forced collapse, 0.12% backtrack) +- 78 propagations (79.96µs), 48 forced collapse (78.16µs), 0 backtrack (0.00ns) + +benchmark 4 (n = 300) +# total time : 116.36ms (43.53% propagation, 56.03% forced collapse, 0.23% backtrack) +- 265 propagations (191.13µs), 183 forced collapse (356.23µs), 4 backtrack (66.91µs) + +benchmark 5 (n = 100) +# total time : 886.94ms (31.81% propagation, 67.66% forced collapse, 0.43% backtrack) +- 743 propagations (379.77µs), 506 forced collapse (1.19ms), 27 backtrack (140.68µs)