Skip to content

Commit ea1e33c

Browse files
committed
add benchmark codes
1 parent 44c8c25 commit ea1e33c

7 files changed

Lines changed: 327 additions & 1 deletion

File tree

cspuz_bench/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ default = []
1212
experimental-backend-glucose-rs = ["cspuz_rs/experimental-backend-glucose-rs", "cspuz_rs_puzzles/experimental-backend-glucose-rs"]
1313

1414
[dependencies]
15-
cspuz_rs = { path = "../cspuz_rs", default-features = false }
15+
cspuz_core = { path = "../cspuz_core", default-features = false }
16+
cspuz_rs = { path = "../cspuz_rs", default-features = false, features = ["generator"]}
1617
cspuz_rs_puzzles = { path = "../cspuz_rs_puzzles", default-features = false }
18+
serde = { version = "1.0", features = ["derive"] }
1719
serde_json = "1.0"
20+
rand = "0.8.5"

cspuz_bench/src/benchmarks/mod.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
use cspuz_core::integration::{reset_thread_local_perf_stats, thread_local_perf_stats};
2+
use cspuz_rs::graph::BoolGridEdgesIrrefutableFacts as CspuzRsBoolGridEdgesIrrefutableFacts;
3+
use serde::{Deserialize, Serialize};
4+
5+
mod slitherlink;
6+
7+
#[derive(Serialize, Deserialize)]
8+
pub struct BoolGridEdgesIrrefutableFacts {
9+
pub horizontal: Vec<Vec<Option<bool>>>,
10+
pub vertical: Vec<Vec<Option<bool>>>,
11+
}
12+
13+
impl BoolGridEdgesIrrefutableFacts {
14+
pub fn to_cspuz_rs(&self) -> CspuzRsBoolGridEdgesIrrefutableFacts {
15+
CspuzRsBoolGridEdgesIrrefutableFacts {
16+
horizontal: self.horizontal.clone(),
17+
vertical: self.vertical.clone(),
18+
}
19+
}
20+
21+
pub fn from_cspuz_rs(facts: &CspuzRsBoolGridEdgesIrrefutableFacts) -> Self {
22+
Self {
23+
horizontal: facts.horizontal.clone(),
24+
vertical: facts.vertical.clone(),
25+
}
26+
}
27+
}
28+
29+
pub struct BenchResult {
30+
pub elapsed_time_seconds: f64,
31+
pub sat_num_propagations: f64,
32+
}
33+
34+
fn accumulate(results: &[BenchResult]) -> BenchResult {
35+
let elapsed_time_seconds = results.iter().map(|r| r.elapsed_time_seconds).sum();
36+
let sat_num_propagations = results.iter().map(|r| r.sat_num_propagations).sum();
37+
BenchResult {
38+
elapsed_time_seconds,
39+
sat_num_propagations,
40+
}
41+
}
42+
43+
#[derive(Debug)]
44+
pub enum BenchmarkError {
45+
AnswerMismatch,
46+
}
47+
48+
fn run_with_bench_result<F, T>(f: F) -> (T, BenchResult)
49+
where
50+
F: FnOnce() -> T,
51+
{
52+
reset_thread_local_perf_stats();
53+
54+
let orig_default_config = cspuz_core::config::Config::default();
55+
let updated_default_config = cspuz_core::config::Config {
56+
record_perf_stats_thread_local: true,
57+
..orig_default_config
58+
};
59+
cspuz_core::config::Config::set_default(updated_default_config);
60+
61+
let start = std::time::Instant::now();
62+
let answer = f();
63+
let elapsed_time_seconds = start.elapsed().as_secs_f64();
64+
65+
let perf_stats = thread_local_perf_stats();
66+
let sat_num_propagations = perf_stats.propagations();
67+
68+
cspuz_core::config::Config::set_default(orig_default_config);
69+
70+
(
71+
answer,
72+
BenchResult {
73+
elapsed_time_seconds,
74+
sat_num_propagations: sat_num_propagations as f64,
75+
},
76+
)
77+
}
78+
79+
#[derive(Serialize, Deserialize)]
80+
#[serde(tag = "type")]
81+
pub enum Task {
82+
Slitherlink(slitherlink::SlitherlinkTask),
83+
}
84+
85+
pub fn run_benchmark(task: &Task) -> Result<BenchResult, BenchmarkError> {
86+
match task {
87+
Task::Slitherlink(task) => slitherlink::run_benchmark(task),
88+
}
89+
}
90+
91+
#[derive(Serialize, Deserialize)]
92+
struct BaseBenchmarkSet {
93+
solve_tasks: Vec<BaseSolveTask>,
94+
generate_tasks: Vec<BaseGenerateTaskSet>,
95+
}
96+
97+
#[derive(Serialize, Deserialize)]
98+
struct BaseSolveTask {
99+
name: String,
100+
puzzle_type: String,
101+
url: String,
102+
}
103+
104+
#[derive(Serialize, Deserialize)]
105+
struct BaseGenerateTaskSet {
106+
name: String,
107+
puzzle_type: String,
108+
height: usize,
109+
width: usize,
110+
seed: u64,
111+
}
112+
113+
#[derive(Serialize, Deserialize)]
114+
struct BenchmarkSet {
115+
solve_tasks: Vec<SolveTask>,
116+
generate_tasks: Vec<GenerateTaskSet>,
117+
}
118+
119+
#[derive(Serialize, Deserialize)]
120+
struct SolveTask {
121+
name: String,
122+
puzzle_type: String,
123+
task: Task,
124+
}
125+
126+
#[derive(Serialize, Deserialize)]
127+
struct GenerateTaskSet {
128+
name: String,
129+
puzzle_type: String,
130+
tasks: Vec<Task>,
131+
}
132+
133+
fn materialize_benchmark_set(base: BaseBenchmarkSet) -> BenchmarkSet {
134+
let solve_tasks = base
135+
.solve_tasks
136+
.into_iter()
137+
.map(|base_task| {
138+
let task = match base_task.puzzle_type.as_str() {
139+
"slitherlink" => slitherlink::materialize_solve_task(&base_task.url),
140+
_ => panic!("Unknown puzzle type: {}", base_task.puzzle_type),
141+
};
142+
SolveTask {
143+
name: base_task.name,
144+
puzzle_type: base_task.puzzle_type,
145+
task,
146+
}
147+
})
148+
.collect();
149+
150+
let generate_tasks = base
151+
.generate_tasks
152+
.into_iter()
153+
.map(|base_task| match base_task.puzzle_type.as_str() {
154+
"slitherlink" => slitherlink::materialize_generate_task(base_task),
155+
_ => panic!("Unknown puzzle type: {}", base_task.puzzle_type),
156+
})
157+
.collect();
158+
159+
BenchmarkSet {
160+
solve_tasks,
161+
generate_tasks,
162+
}
163+
}
164+
165+
pub fn run_materialize(src_path: &str, dest_path: &str) -> Result<(), Box<dyn std::error::Error>> {
166+
let base_benchmark_set: BaseBenchmarkSet =
167+
serde_json::from_reader(std::fs::File::open(src_path)?)?;
168+
let benchmark_set = materialize_benchmark_set(base_benchmark_set);
169+
serde_json::to_writer_pretty(std::fs::File::create(dest_path)?, &benchmark_set)?;
170+
Ok(())
171+
}
172+
173+
pub fn run_benchmarks(src_path: &str) {
174+
let benchmark_set: BenchmarkSet =
175+
serde_json::from_reader(std::fs::File::open(src_path).unwrap()).unwrap();
176+
177+
for solve_task in &benchmark_set.solve_tasks {
178+
println!("Running solve benchmark: {}", solve_task.name);
179+
let result = run_benchmark(&solve_task.task).unwrap();
180+
println!(
181+
"Elapsed time: {:.3} seconds, SAT propagations: {}",
182+
result.elapsed_time_seconds, result.sat_num_propagations
183+
);
184+
}
185+
186+
for generate_task_set in &benchmark_set.generate_tasks {
187+
println!("Running generate benchmark set: {}", generate_task_set.name);
188+
189+
let mut results = vec![];
190+
for task in &generate_task_set.tasks {
191+
let result = run_benchmark(task).unwrap();
192+
results.push(result);
193+
}
194+
let accumulated_result = accumulate(&results);
195+
println!(
196+
"Accumulated result for generate benchmark set {}: Elapsed time: {:.3} seconds, SAT propagations: {}",
197+
generate_task_set.name, accumulated_result.elapsed_time_seconds, accumulated_result.sat_num_propagations
198+
);
199+
}
200+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use cspuz_rs::generator;
2+
use cspuz_rs_puzzles::puzzles::slitherlink::{deserialize_problem, solve_slitherlink};
3+
4+
use rand::SeedableRng;
5+
use serde::{Deserialize, Serialize};
6+
use std::cell::RefCell;
7+
8+
#[derive(Serialize, Deserialize)]
9+
pub struct SlitherlinkTask {
10+
pub problem: Vec<Vec<Option<i32>>>,
11+
pub expectation: Option<super::BoolGridEdgesIrrefutableFacts>,
12+
}
13+
14+
pub(super) fn run_benchmark(
15+
task: &SlitherlinkTask,
16+
) -> Result<super::BenchResult, super::BenchmarkError> {
17+
let problem = task.problem.clone();
18+
let expectation = task.expectation.as_ref().map(|e| e.to_cspuz_rs());
19+
20+
let (answer, bench_result) =
21+
super::run_with_bench_result(|| solve_slitherlink(false, &problem));
22+
if answer != expectation {
23+
Err(super::BenchmarkError::AnswerMismatch)
24+
} else {
25+
Ok(bench_result)
26+
}
27+
}
28+
29+
pub(super) fn materialize_solve_task(url: &str) -> super::Task {
30+
let (full, problem) = deserialize_problem(url).expect("Failed to deserialize problem");
31+
assert_eq!(full, false);
32+
33+
let expectation = solve_slitherlink(false, &problem)
34+
.map(|ans| super::BoolGridEdgesIrrefutableFacts::from_cspuz_rs(&ans));
35+
super::Task::Slitherlink(SlitherlinkTask {
36+
problem,
37+
expectation,
38+
})
39+
}
40+
41+
pub(super) fn materialize_generate_task(
42+
base: super::BaseGenerateTaskSet,
43+
) -> super::GenerateTaskSet {
44+
assert_eq!(base.puzzle_type, "slitherlink");
45+
46+
let height = base.height;
47+
let width = base.width;
48+
let pattern =
49+
vec![
50+
vec![
51+
generator::Choice::new(vec![None, Some(0), Some(1), Some(2), Some(3)], None);
52+
width
53+
];
54+
height
55+
];
56+
57+
let trajectory = RefCell::new(vec![]);
58+
let solve_with_record = |problem: &Vec<Vec<Option<i32>>>| {
59+
let answer = solve_slitherlink(false, problem);
60+
61+
trajectory
62+
.borrow_mut()
63+
.push((problem.clone(), answer.clone()));
64+
65+
answer
66+
};
67+
68+
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
69+
let _ = generator::Generator::new(
70+
solve_with_record,
71+
pattern,
72+
generator::default_uniqueness_checker(),
73+
generator::default_scorer(None, 5.0),
74+
)
75+
.generate(&mut rng);
76+
77+
let tasks = trajectory
78+
.into_inner()
79+
.into_iter()
80+
.map(|(problem, answer)| {
81+
let expectation =
82+
answer.map(|ans| super::BoolGridEdgesIrrefutableFacts::from_cspuz_rs(&ans));
83+
super::Task::Slitherlink(SlitherlinkTask {
84+
problem,
85+
expectation,
86+
})
87+
})
88+
.collect::<Vec<_>>();
89+
super::GenerateTaskSet {
90+
name: base.name,
91+
puzzle_type: base.puzzle_type,
92+
tasks,
93+
}
94+
}

cspuz_bench/src/bin/bench.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fn main() {
2+
// cspuz_bench/tasks/base.json -> cspuz_bench/tasks/bench.json
3+
// NOTE: relative to this file, not the current working directory
4+
5+
cspuz_bench::benchmarks::run_benchmarks(concat!(
6+
env!("CARGO_MANIFEST_DIR"),
7+
"/tasks/bench.json"
8+
));
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
cspuz_bench::benchmarks::run_materialize(
3+
concat!(env!("CARGO_MANIFEST_DIR"), "/tasks/base.json"),
4+
concat!(env!("CARGO_MANIFEST_DIR"), "/tasks/bench.json"),
5+
)
6+
.unwrap();
7+
}

cspuz_bench/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod benchmarks;

cspuz_bench/tasks/base.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"solve_tasks": [],
3+
"generate_tasks": [
4+
{
5+
"name": "slitherlink_10x10",
6+
"puzzle_type": "slitherlink",
7+
"height": 10,
8+
"width": 10,
9+
"seed": 42
10+
}
11+
]
12+
}

0 commit comments

Comments
 (0)