///|
/// A deterministic solver benchmark result.
pub struct BenchmarkResult {
name : String
solved : Bool
solutions : Int
nodes : Int
propagations : Int
checks : Int
pruned : Int
depth : Int
repeat_count : Int
}
///|
/// Construct a benchmark result from solver counters.
pub fn benchmark_result(
name : String,
solved : Bool,
solutions : Int,
stats : SearchStats,
repeat_count : Int,
) -> BenchmarkResult {
{
name,
solved,
solutions,
nodes: stats.node_count(),
propagations: stats.propagation_count(),
checks: stats.check_count(),
pruned: stats.pruned_count(),
depth: stats.depth(),
repeat_count,
}
}
///|
/// Return the benchmark name.
pub fn BenchmarkResult::name(self : BenchmarkResult) -> String {
self.name
}
///|
/// Return whether the case produced the expected satisfiability result.
pub fn BenchmarkResult::solved(self : BenchmarkResult) -> Bool {
self.solved
}
///|
/// Return the number of collected solutions.
pub fn BenchmarkResult::solutions(self : BenchmarkResult) -> Int {
self.solutions
}
///|
/// Return visited node count.
pub fn BenchmarkResult::nodes(self : BenchmarkResult) -> Int {
self.nodes
}
///|
/// Return propagation count.
pub fn BenchmarkResult::propagations(self : BenchmarkResult) -> Int {
self.propagations
}
///|
/// Return constraint-check count.
pub fn BenchmarkResult::checks(self : BenchmarkResult) -> Int {
self.checks
}
///|
/// Return pruned-value count.
pub fn BenchmarkResult::pruned(self : BenchmarkResult) -> Int {
self.pruned
}
///|
/// Return maximum depth.
pub fn BenchmarkResult::depth(self : BenchmarkResult) -> Int {
self.depth
}
///|
/// Return number of repetitions.
pub fn BenchmarkResult::repeat_count(self : BenchmarkResult) -> Int {
self.repeat_count
}
///|
/// Return a stable one-line benchmark record.
pub fn BenchmarkResult::describe(self : BenchmarkResult) -> String {
"\{self.name}: solved=\{self.solved}, solutions=\{self.solutions}, nodes=\{self.nodes}, propagations=\{self.propagations}, checks=\{self.checks}, pruned=\{self.pruned}, depth=\{self.depth}, repeats=\{self.repeat_count}"
}
///|
/// Return the deterministic work score used when wall-clock noise is high.
pub fn BenchmarkResult::work(self : BenchmarkResult) -> Int {
self.nodes + self.propagations + self.checks + self.pruned
}
///|
/// Aggregate counters across repeated deterministic runs.
pub fn repeat_benchmark(
name : String,
repeats : Int,
run : () -> BenchmarkResult,
) -> BenchmarkResult {
let count = if repeats < 1 { 1 } else { repeats }
let first = run()
let mut nodes = first.nodes
let mut propagations = first.propagations
let mut checks = first.checks
let mut pruned = first.pruned
let mut solutions = first.solutions
let mut depth = first.depth
let mut solved = first.solved
for _ in 1.. depth {
depth = current.depth
}
solved = solved && current.solved
}
{
name,
solved,
solutions,
nodes,
propagations,
checks,
pruned,
depth,
repeat_count: count,
}
}
///|
/// Run the canonical N-Queens benchmark.
pub fn benchmark_n_queens(size : Int) -> BenchmarkResult {
match n_queens(size) {
None =>
benchmark_result("nqueens_\{size}", false, 0, empty_search_stats(), 1)
Some(problem) => {
let solutions = problem.solve_all(1)
benchmark_result(
"nqueens_\{size}",
solutions.length() > 0,
solutions.length(),
problem.stats(),
1,
)
}
}
}
///|
/// Run the canonical Sudoku benchmark.
pub fn benchmark_sudoku() -> BenchmarkResult {
match classic_sudoku() {
None =>
benchmark_result("sudoku_classic", false, 0, empty_search_stats(), 1)
Some(problem) => {
let solutions = problem.solve_all(1)
benchmark_result(
"sudoku_classic",
solutions.length() > 0,
solutions.length(),
problem.stats(),
1,
)
}
}
}
///|
/// Run a small Latin-square benchmark.
pub fn benchmark_latin(size : Int) -> BenchmarkResult {
match latin_square(size) {
None => benchmark_result("latin_\{size}", false, 0, empty_search_stats(), 1)
Some(problem) => {
problem.fix_first_row()
let solutions = problem.solve()
benchmark_result(
"latin_\{size}",
solutions is Some(_),
if solutions is Some(_) {
1
} else {
0
},
problem.stats(),
1,
)
}
}
}
///|
/// Run a grid coloring benchmark.
pub fn benchmark_coloring(rows : Int, columns : Int) -> BenchmarkResult {
match grid_coloring(rows, columns, 3) {
None => benchmark_result("grid_coloring", false, 0, empty_search_stats(), 1)
Some(problem) => {
let solutions = problem.solve_all(1)
benchmark_result(
"grid_coloring_\{rows}x\{columns}",
solutions.length() > 0,
solutions.length(),
problem.stats(),
1,
)
}
}
}
///|
/// Run a small balanced-schedule benchmark.
pub fn benchmark_schedule() -> BenchmarkResult {
match balanced_schedule(4, 4, 2) {
None =>
benchmark_result("schedule_4x4x2", false, 0, empty_search_stats(), 1)
Some(problem) => {
ignore(problem.avoid_same_shift(0))
let solutions = problem.solve_all(1)
benchmark_result(
"schedule_4x4x2",
solutions.length() > 0,
solutions.length(),
problem.stats(),
1,
)
}
}
}
///|
/// Run a small knapsack benchmark.
pub fn benchmark_knapsack() -> BenchmarkResult {
match
knapsack(
["camera", "tripod", "lens", "battery"],
[4, 3, 2, 1],
[9, 5, 7, 3],
6,
) {
None =>
benchmark_result("knapsack_4_items", false, 0, empty_search_stats(), 1)
Some(problem) => {
let result = problem.solve()
benchmark_result(
"knapsack_4_items",
result is Some(_),
if result is Some(_) {
1
} else {
0
},
problem.stats(),
1,
)
}
}
}
///|
/// Return the canonical benchmark set.
pub fn benchmark_suite() -> Array[BenchmarkResult] {
[
benchmark_n_queens(8),
benchmark_sudoku(),
benchmark_latin(4),
benchmark_coloring(3, 3),
benchmark_schedule(),
benchmark_knapsack(),
]
}
///|
/// Render benchmark records as Markdown table rows.
pub fn benchmark_markdown(results : Array[BenchmarkResult]) -> String {
let builder = StringBuilder()
builder.write_string(
"| Case | Solved | Solutions | Nodes | Propagations | Checks | Pruned | Depth |\n",
)
builder.write_string(
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |\n",
)
for result in results {
builder.write_string(
"| \{result.name} | \{result.solved} | \{result.solutions} | \{result.nodes} | \{result.propagations} | \{result.checks} | \{result.pruned} | \{result.depth} |\n",
)
}
builder.to_string()
}
///|
/// Render the suite as plain text for CI logs.
pub fn benchmark_report(results : Array[BenchmarkResult]) -> String {
let builder = StringBuilder()
for index, result in results {
if index > 0 {
builder.write_char('\n')
}
builder.write_string(result.describe())
}
builder.to_string()
}
///|
/// Return whether every benchmark in a suite solved successfully.
pub fn benchmarks_pass(results : Array[BenchmarkResult]) -> Bool {
for result in results {
if !result.solved {
return false
}
}
true
}
///|
/// Return total deterministic work across a suite.
pub fn benchmark_work(results : Array[BenchmarkResult]) -> Int {
results.fold(init=0, (total, result) => total + result.work())
}
///|
/// Return a stable fingerprint useful for regression tests.
pub fn benchmark_fingerprint(results : Array[BenchmarkResult]) -> String {
let builder = StringBuilder()
for result in results {
builder.write_string("\{result.name}:\{result.nodes}:\{result.pruned};")
}
builder.to_string()
}