sudoku-smt-solvers 0.1.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. sudoku_smt_solvers-0.1.0/LICENSE +21 -0
  2. sudoku_smt_solvers-0.1.0/PKG-INFO +161 -0
  3. sudoku_smt_solvers-0.1.0/README.md +143 -0
  4. sudoku_smt_solvers-0.1.0/pyproject.toml +30 -0
  5. sudoku_smt_solvers-0.1.0/setup.cfg +4 -0
  6. sudoku_smt_solvers-0.1.0/setup.py +57 -0
  7. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/__init__.py +4 -0
  8. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/benchmarks/__init__.py +5 -0
  9. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/benchmarks/benchmark_runner.py +211 -0
  10. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/solvers/__init__.py +4 -0
  11. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/solvers/cvc5_solver.py +207 -0
  12. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/solvers/dpll_solver.py +175 -0
  13. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/solvers/dpllt_solver.py +211 -0
  14. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/solvers/sudoku_error.py +8 -0
  15. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers/solvers/z3_solver.py +160 -0
  16. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers.egg-info/PKG-INFO +161 -0
  17. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers.egg-info/SOURCES.txt +29 -0
  18. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers.egg-info/dependency_links.txt +1 -0
  19. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers.egg-info/requires.txt +3 -0
  20. sudoku_smt_solvers-0.1.0/sudoku_smt_solvers.egg-info/top_level.txt +1 -0
  21. sudoku_smt_solvers-0.1.0/tests/test_benchmark_runner.py +152 -0
  22. sudoku_smt_solvers-0.1.0/tests/test_cvc5_solver.py +149 -0
  23. sudoku_smt_solvers-0.1.0/tests/test_dfs_solver.py +67 -0
  24. sudoku_smt_solvers-0.1.0/tests/test_dpll_solver.py +78 -0
  25. sudoku_smt_solvers-0.1.0/tests/test_dpllt_solver.py +77 -0
  26. sudoku_smt_solvers-0.1.0/tests/test_hole_digger.py +138 -0
  27. sudoku_smt_solvers-0.1.0/tests/test_las_vegas.py +141 -0
  28. sudoku_smt_solvers-0.1.0/tests/test_parser.py +0 -0
  29. sudoku_smt_solvers-0.1.0/tests/test_profiler.py +0 -0
  30. sudoku_smt_solvers-0.1.0/tests/test_sudoku_generator.py +79 -0
  31. sudoku_smt_solvers-0.1.0/tests/test_z3_solver.py +93 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 liamjdavis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.1
2
+ Name: sudoku_smt_solvers
3
+ Version: 0.1.0
4
+ Summary: A collection of SAT and SMT solvers for solving Sudoku puzzles
5
+ Home-page: https://liamjdavis.github.io/sudoku-smt-solvers
6
+ Author: Liam Davis
7
+ Author-email: ljdavis27@amherst.edu
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: cvc5
16
+ Requires-Dist: pysat
17
+ Requires-Dist: z3-solver
18
+
19
+ # Sudoku-SMT-Solvers
20
+
21
+ [![Pytest + CI/CD](https://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/test.yml/badge.svg)](ttps://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/test.yml)
22
+ [![Coverage Status](https://coveralls.io/repos/github/liamjdavis/Sudoku-SMT-Solvers/badge.svg)](https://coveralls.io/github/liamjdavis/Sudoku-SMT-Solvers)
23
+ [![Docs Build Deployment](https://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/docs.yml/badge.svg)](https://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/docs.yml)
24
+ [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://liamjdavis.github.io/sudoku-smt-solvers)
25
+
26
+
27
+ ## About
28
+ This repository contains the code for the study "Evaluating SMT-Based Solvers on Sudoku". Created by Liam Davis (@liamjdavis) and Ryan Ji (@TairanJ) as their for COSC-241 Artificial Intelligence at Amherst College, it evaluates the efficacy of SMT-Based Solvers by benchmarking three modern SMT solvers (DPLL(T), Z3, and CVC5) against the DPLL algorithm on a collection of 100 25x25 Sudoku puzzles of varying difficulty.
29
+
30
+ Along with the study, we also published `sudoku-smt-solvers`, a Python package that provides the various SMT-based Sudoku solvers and benchmarking tools we built for this study. The package features DPLL(T), Z3, and CVC5 solvers optimized for 25x25 Sudoku puzzles, a puzzle generator for creating test cases, and a comprehensive benchmarking suite. Available through pip, it offers a simple API for solving Sudoku puzzles using state-of-the-art SMT solvers while facilitating performance comparisons between different solving approaches.
31
+
32
+ The study aims to answer three research questions:
33
+ 1. How have logical solvers evolved over time in terms of performance and capability?
34
+ 2. How do different encodings of Sudoku affect the efficiency and scalability of these solvers?
35
+ 3. Are there specific features or optimizations in SMT solvers that provide a significant advantage over traditional SAT solvers for this class of problem?
36
+
37
+ ## Getting started
38
+ ### Installation
39
+ To run the code locally, you can install with `pip`
40
+
41
+ ```bash
42
+ pip install sudoku-smt-solvers
43
+ ```
44
+
45
+ ### Solvers
46
+ This package includes the DPLL solver and three modern SMT solvers:
47
+ * DPLL(T)
48
+ * CVC5
49
+ * Z3
50
+
51
+ To run any of the solvers on a 25x25 Sudoku puzzle, you can create an instance of the solver class and call the solve method in a file at the root (Sudoku-smt-solvers). Here is an example using Z3:
52
+
53
+ ```python
54
+ from sudoku_smt_solvers.solvers.z3_solver import Z3Solver
55
+
56
+ # Example grid (25x25)
57
+ grid = [[0] * 25 for _ in range(25)]
58
+ solver = Z3Solver(grid)
59
+ solution = solver.solve()
60
+
61
+ if solution:
62
+ print(f"Solution:\n\n{solution}")
63
+ else:
64
+ print("No solution exists.")
65
+ ```
66
+
67
+ ### Sudoku Generator
68
+ This package also includes a generator for creating Sudoku puzzles to be used as benchmarks. To generate a puzzle, create an instance of the `SudokuGenerator` class and call the `generate` method. Here is an example:
69
+
70
+ ```python
71
+ from sudoku_smt_solvers.benchmarks.sudoku_generator.sudoku_generator import SudokuGenerator
72
+
73
+ generator = SudokuGenerator(size = 25, givens = 80, timeout = 5, difficulty = "Medium", puzzles_dir = "benchmarks/puzzles", solutions_dir = "benchmarks/solutions")
74
+
75
+ generator.generate()
76
+ ```
77
+
78
+ Due to the computational complexity of generating large sudoku puzzles, it is recommended that you run multiple generator instances in parallel to create benchmarks.
79
+
80
+ ### Benchmark Runner
81
+ To run the benchmarks you created on all four solvers, create an instance of the `BenchmarkRunner` class and call the `run_benchmarks` method. Here is an example:
82
+
83
+ ```python
84
+ from sudoku_smt_solvers.benchmarks.benchmark_runner import BenchmarkRunner
85
+
86
+ runner = BenchmarkRunner(
87
+ puzzles_dir='resources/benchmarks/puzzles/',
88
+ solutions_dir='resources/benchmarks/solutions/',
89
+ results_dir='results/'
90
+ )
91
+ runner.run_benchmarks()
92
+ ```
93
+
94
+ ## Contributing
95
+
96
+ We welcome contributions in the form of new solvers, additions to our benchmark suite, or anything that improves the tool! Here's how to get started:
97
+
98
+ ### Development Setup
99
+
100
+ 1. **Fork and Clone**:
101
+ Begin by forking the repository and cloning your fork locally:
102
+ ```bash
103
+ git clone https://github.com/yourusername/Sudoku-SMT-Solvers.git
104
+ cd Sudoku-SMT-Solvers
105
+ ```
106
+
107
+ 2. **Create and Activate a Virtual Environment**:
108
+ Set up a Python virtual environment to isolate your dependencies:
109
+ ```bash
110
+ python3 -m venv venv
111
+ source venv/bin/activate # On Windows, use `venv\Scripts\activate`
112
+ ```
113
+
114
+ 3. **Install Dependencies**:
115
+ Install the required dependencies from the `requirements.txt` file:
116
+ ```bash
117
+ pip install -r requirements.txt
118
+ ```
119
+
120
+ 4. **Set Up Pre-Commit Hooks**:
121
+ Install and configure pre-commit hooks to maintain code quality:
122
+ ```bash
123
+ pip install pre-commit
124
+ pre-commit install
125
+ ```
126
+
127
+ To manually run the hooks and verify code compliance, use:
128
+ ```bash
129
+ pre-commit run
130
+ ```
131
+
132
+ 5. **Testing and Coverage Requirements**:
133
+ - Write tests for any new code or modifications.
134
+ - Use `pytest` for running tests:
135
+ ```bash
136
+ pytest
137
+ ```
138
+ - Ensure the test coverage is at least 90%:
139
+
140
+ 6. **Add and Commit Your Changes**:
141
+ - Follow the existing code style and structure.
142
+ - Verify that all pre-commit hooks pass and the test coverage meets the minimum requirement.
143
+ ```bash
144
+ git add .
145
+ git commit -m "Description of your changes"
146
+ ```
147
+
148
+ 7. **Push Your Branch**:
149
+ Push your changes to your forked repository:
150
+ ```bash
151
+ git push origin your-branch-name
152
+ ```
153
+
154
+ 8. **Open a PR for us to review**
155
+ ---
156
+
157
+ Thank you for your interest in contributing to Sudoku-SMT-Solvers! Your efforts help make this project better for everyone.
158
+
159
+
160
+ ## Contact Us
161
+ For any questions or support, please reach out to Liam at ljdavis27 at amherst.edu and Ryan at tji26 at amherst.edu
@@ -0,0 +1,143 @@
1
+ # Sudoku-SMT-Solvers
2
+
3
+ [![Pytest + CI/CD](https://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/test.yml/badge.svg)](ttps://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/test.yml)
4
+ [![Coverage Status](https://coveralls.io/repos/github/liamjdavis/Sudoku-SMT-Solvers/badge.svg)](https://coveralls.io/github/liamjdavis/Sudoku-SMT-Solvers)
5
+ [![Docs Build Deployment](https://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/docs.yml/badge.svg)](https://github.com/liamjdavis/Sudoku-SMT-Solvers/actions/workflows/docs.yml)
6
+ [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://liamjdavis.github.io/sudoku-smt-solvers)
7
+
8
+
9
+ ## About
10
+ This repository contains the code for the study "Evaluating SMT-Based Solvers on Sudoku". Created by Liam Davis (@liamjdavis) and Ryan Ji (@TairanJ) as their for COSC-241 Artificial Intelligence at Amherst College, it evaluates the efficacy of SMT-Based Solvers by benchmarking three modern SMT solvers (DPLL(T), Z3, and CVC5) against the DPLL algorithm on a collection of 100 25x25 Sudoku puzzles of varying difficulty.
11
+
12
+ Along with the study, we also published `sudoku-smt-solvers`, a Python package that provides the various SMT-based Sudoku solvers and benchmarking tools we built for this study. The package features DPLL(T), Z3, and CVC5 solvers optimized for 25x25 Sudoku puzzles, a puzzle generator for creating test cases, and a comprehensive benchmarking suite. Available through pip, it offers a simple API for solving Sudoku puzzles using state-of-the-art SMT solvers while facilitating performance comparisons between different solving approaches.
13
+
14
+ The study aims to answer three research questions:
15
+ 1. How have logical solvers evolved over time in terms of performance and capability?
16
+ 2. How do different encodings of Sudoku affect the efficiency and scalability of these solvers?
17
+ 3. Are there specific features or optimizations in SMT solvers that provide a significant advantage over traditional SAT solvers for this class of problem?
18
+
19
+ ## Getting started
20
+ ### Installation
21
+ To run the code locally, you can install with `pip`
22
+
23
+ ```bash
24
+ pip install sudoku-smt-solvers
25
+ ```
26
+
27
+ ### Solvers
28
+ This package includes the DPLL solver and three modern SMT solvers:
29
+ * DPLL(T)
30
+ * CVC5
31
+ * Z3
32
+
33
+ To run any of the solvers on a 25x25 Sudoku puzzle, you can create an instance of the solver class and call the solve method in a file at the root (Sudoku-smt-solvers). Here is an example using Z3:
34
+
35
+ ```python
36
+ from sudoku_smt_solvers.solvers.z3_solver import Z3Solver
37
+
38
+ # Example grid (25x25)
39
+ grid = [[0] * 25 for _ in range(25)]
40
+ solver = Z3Solver(grid)
41
+ solution = solver.solve()
42
+
43
+ if solution:
44
+ print(f"Solution:\n\n{solution}")
45
+ else:
46
+ print("No solution exists.")
47
+ ```
48
+
49
+ ### Sudoku Generator
50
+ This package also includes a generator for creating Sudoku puzzles to be used as benchmarks. To generate a puzzle, create an instance of the `SudokuGenerator` class and call the `generate` method. Here is an example:
51
+
52
+ ```python
53
+ from sudoku_smt_solvers.benchmarks.sudoku_generator.sudoku_generator import SudokuGenerator
54
+
55
+ generator = SudokuGenerator(size = 25, givens = 80, timeout = 5, difficulty = "Medium", puzzles_dir = "benchmarks/puzzles", solutions_dir = "benchmarks/solutions")
56
+
57
+ generator.generate()
58
+ ```
59
+
60
+ Due to the computational complexity of generating large sudoku puzzles, it is recommended that you run multiple generator instances in parallel to create benchmarks.
61
+
62
+ ### Benchmark Runner
63
+ To run the benchmarks you created on all four solvers, create an instance of the `BenchmarkRunner` class and call the `run_benchmarks` method. Here is an example:
64
+
65
+ ```python
66
+ from sudoku_smt_solvers.benchmarks.benchmark_runner import BenchmarkRunner
67
+
68
+ runner = BenchmarkRunner(
69
+ puzzles_dir='resources/benchmarks/puzzles/',
70
+ solutions_dir='resources/benchmarks/solutions/',
71
+ results_dir='results/'
72
+ )
73
+ runner.run_benchmarks()
74
+ ```
75
+
76
+ ## Contributing
77
+
78
+ We welcome contributions in the form of new solvers, additions to our benchmark suite, or anything that improves the tool! Here's how to get started:
79
+
80
+ ### Development Setup
81
+
82
+ 1. **Fork and Clone**:
83
+ Begin by forking the repository and cloning your fork locally:
84
+ ```bash
85
+ git clone https://github.com/yourusername/Sudoku-SMT-Solvers.git
86
+ cd Sudoku-SMT-Solvers
87
+ ```
88
+
89
+ 2. **Create and Activate a Virtual Environment**:
90
+ Set up a Python virtual environment to isolate your dependencies:
91
+ ```bash
92
+ python3 -m venv venv
93
+ source venv/bin/activate # On Windows, use `venv\Scripts\activate`
94
+ ```
95
+
96
+ 3. **Install Dependencies**:
97
+ Install the required dependencies from the `requirements.txt` file:
98
+ ```bash
99
+ pip install -r requirements.txt
100
+ ```
101
+
102
+ 4. **Set Up Pre-Commit Hooks**:
103
+ Install and configure pre-commit hooks to maintain code quality:
104
+ ```bash
105
+ pip install pre-commit
106
+ pre-commit install
107
+ ```
108
+
109
+ To manually run the hooks and verify code compliance, use:
110
+ ```bash
111
+ pre-commit run
112
+ ```
113
+
114
+ 5. **Testing and Coverage Requirements**:
115
+ - Write tests for any new code or modifications.
116
+ - Use `pytest` for running tests:
117
+ ```bash
118
+ pytest
119
+ ```
120
+ - Ensure the test coverage is at least 90%:
121
+
122
+ 6. **Add and Commit Your Changes**:
123
+ - Follow the existing code style and structure.
124
+ - Verify that all pre-commit hooks pass and the test coverage meets the minimum requirement.
125
+ ```bash
126
+ git add .
127
+ git commit -m "Description of your changes"
128
+ ```
129
+
130
+ 7. **Push Your Branch**:
131
+ Push your changes to your forked repository:
132
+ ```bash
133
+ git push origin your-branch-name
134
+ ```
135
+
136
+ 8. **Open a PR for us to review**
137
+ ---
138
+
139
+ Thank you for your interest in contributing to Sudoku-SMT-Solvers! Your efforts help make this project better for everyone.
140
+
141
+
142
+ ## Contact Us
143
+ For any questions or support, please reach out to Liam at ljdavis27 at amherst.edu and Ryan at tji26 at amherst.edu
@@ -0,0 +1,30 @@
1
+ [tool.pre-commit]
2
+ config = ".pre-commit-config.yaml"
3
+
4
+ [tool.black]
5
+ line-length = 88
6
+ target-version = ["py312"]
7
+ exclude = '''
8
+ /(
9
+ \.direnv
10
+ | \.eggs
11
+ | \.git
12
+ | \.hg
13
+ | \.ipynb_checkpoints
14
+ | \.mypy_cache
15
+ | \.nox
16
+ | \.pytest_cache
17
+ | \.ruff_cache
18
+ | \.tox
19
+ | \.env
20
+ | \.svn
21
+ | __pypackages__
22
+ | _build
23
+ | buck-out
24
+ | build
25
+ | dist
26
+ | venv
27
+ | sudoku_smt_solvers/solvers/sudoku_error\.py
28
+ | tests/conftest\.py
29
+ )/
30
+ '''
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,57 @@
1
+ import os
2
+ import setuptools
3
+ import re
4
+
5
+ NAME = "sudoku_smt_solvers"
6
+ AUTHOR = "Liam Davis"
7
+ AUTHOR_EMAIL = "ljdavis27@amherst.edu"
8
+ DESCRIPTION = "A collection of SAT and SMT solvers for solving Sudoku puzzles"
9
+ LICENSE = "MIT"
10
+ URL = "https://liamjdavis.github.io/sudoku-smt-solvers"
11
+ README = "README.md"
12
+ CLASSIFIERS = [
13
+ "Programming Language :: Python :: 3",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ INSTALL_REQUIRES = [
18
+ "cvc5",
19
+ "pysat",
20
+ "z3-solver",
21
+ ]
22
+ ENTRY_POINTS = {}
23
+ SCRIPTS = []
24
+
25
+ HERE = os.path.dirname(__file__)
26
+
27
+
28
+ def read(file):
29
+ with open(os.path.join(HERE, file), "r") as fh:
30
+ return fh.read()
31
+
32
+
33
+ VERSION = re.search(
34
+ r'__version__ = [\'"]([^\'"]*)[\'"]', read(NAME.replace("-", "_") + "/__init__.py")
35
+ ).group(1)
36
+ LONG_DESCRIPTION = read(README)
37
+
38
+
39
+ if __name__ == "__main__":
40
+ setuptools.setup(
41
+ name=NAME,
42
+ version=VERSION,
43
+ packages=setuptools.find_packages(),
44
+ author=AUTHOR,
45
+ author_email=AUTHOR_EMAIL,
46
+ description=DESCRIPTION,
47
+ long_description=LONG_DESCRIPTION,
48
+ long_description_content_type="text/markdown",
49
+ license=LICENSE,
50
+ url=URL,
51
+ classifiers=CLASSIFIERS,
52
+ install_requires=INSTALL_REQUIRES,
53
+ entry_points=ENTRY_POINTS,
54
+ scripts=SCRIPTS,
55
+ include_package_data=True,
56
+ python_requires=">=3.9",
57
+ )
@@ -0,0 +1,4 @@
1
+ from .benchmarks import BenchmarkRunner, SudokuGenerator
2
+ from .solvers import CVC5Solver, DPLLSolver, Z3Solver
3
+
4
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ from .benchmark_runner import BenchmarkRunner
2
+ from .sudoku_generator.dfs_solver import DFSSolver
3
+ from .sudoku_generator.sudoku_generator import SudokuGenerator
4
+ from .sudoku_generator.las_vegas import LasVegasGenerator
5
+ from .sudoku_generator.hole_digger import HoleDigger
@@ -0,0 +1,211 @@
1
+ import json
2
+ import os
3
+ import time
4
+ import multiprocessing
5
+ from typing import Dict, List, Optional
6
+
7
+ from ..solvers import CVC5Solver, DPLLSolver, DPLLTSolver, Z3Solver
8
+
9
+
10
+ class BenchmarkRunner:
11
+ """A benchmark runner for comparing different Sudoku solver implementations.
12
+
13
+ This class manages running performance benchmarks across multiple Sudoku solvers,
14
+ collecting metrics like solve time and propagation counts, and saving results
15
+ to CSV files.
16
+
17
+ Attributes:
18
+ puzzles_dir (str): Directory containing puzzle JSON files
19
+ results_dir (str): Directory where benchmark results are saved
20
+ timeout (int): Maximum time in seconds allowed for each solver attempt
21
+ solvers (dict): Dictionary mapping solver names to solver classes
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ puzzles_dir: str = "benchmarks/puzzles",
27
+ results_dir: str = "benchmarks/results",
28
+ timeout: int = 120,
29
+ ):
30
+ """Initialize the benchmark runner.
31
+
32
+ Args:
33
+ puzzles_dir: Directory containing puzzle JSON files
34
+ results_dir: Directory where benchmark results will be saved
35
+ timeout: Maximum time in seconds allowed for each solver attempt
36
+ """
37
+ self.puzzles_dir = puzzles_dir
38
+ self.results_dir = results_dir
39
+ self.timeout = timeout
40
+ self.solvers = {
41
+ "CVC5": CVC5Solver,
42
+ "DPLL": DPLLSolver,
43
+ "DPLL(T)": DPLLTSolver,
44
+ "Z3": Z3Solver,
45
+ }
46
+ os.makedirs(results_dir, exist_ok=True)
47
+
48
+ def load_puzzle(self, puzzle_id: str) -> Optional[List[List[int]]]:
49
+ puzzle_path = os.path.join(self.puzzles_dir, f"{puzzle_id}.json")
50
+ try:
51
+ with open(puzzle_path, "r") as f:
52
+ data = json.load(f)
53
+ for key in ["grid", "puzzle", "gridc"]:
54
+ if key in data:
55
+ return data[key]
56
+ print(
57
+ f"No valid grid found in {puzzle_id}. Available keys: {list(data.keys())}"
58
+ )
59
+ return None
60
+ except Exception as e:
61
+ print(f"Error loading puzzle {puzzle_id}: {e}")
62
+ return None
63
+
64
+ def _solve_with_timeout(self, solver_class, puzzle, queue):
65
+ solver = solver_class(puzzle)
66
+ result = solver.solve()
67
+ # Pack both the result and propagation count
68
+ queue.put((result, getattr(solver, "propagated_clauses", 0)))
69
+
70
+ def run_solver(self, solver_name: str, puzzle: List[List[int]]) -> Dict:
71
+ """Run a single solver on a puzzle and collect results with timeout.
72
+
73
+ Args:
74
+ solver_name: Name of the solver to use
75
+ puzzle: 2D list representing the Sudoku puzzle
76
+
77
+ Returns:
78
+ Dict containing:
79
+ status: 'sat', 'unsat', 'timeout', or 'error'
80
+ solve_time: Time taken in seconds
81
+ propagations: Number of clause propagations (if available)
82
+ """
83
+ solver_class = self.solvers[solver_name]
84
+
85
+ # Create queue for getting results
86
+ ctx = multiprocessing.get_context("spawn")
87
+ queue = ctx.Queue()
88
+
89
+ # Create process for solving
90
+ process = ctx.Process(
91
+ target=self._solve_with_timeout, args=(solver_class, puzzle, queue)
92
+ )
93
+
94
+ start_time = time.time()
95
+ process.start()
96
+ process.join(timeout=self.timeout)
97
+
98
+ solve_time = time.time() - start_time
99
+
100
+ if process.is_alive():
101
+ process.terminate()
102
+ process.join()
103
+ return {"status": "timeout", "solve_time": self.timeout, "propagations": 0}
104
+
105
+ # Get result and propagation count from queue
106
+ try:
107
+ result, propagations = queue.get_nowait()
108
+ return {
109
+ "status": "sat" if result else "unsat",
110
+ "solve_time": solve_time,
111
+ "propagations": propagations,
112
+ }
113
+ except:
114
+ return {"status": "error", "solve_time": solve_time, "propagations": 0}
115
+
116
+ def run_benchmarks(self) -> None:
117
+ """Run all solvers on all puzzles and save results.
118
+
119
+ Executes benchmarks for each solver on each puzzle, collecting performance
120
+ metrics and saving results to a timestamped CSV file.
121
+
122
+ The CSV output includes:
123
+ - Solver name
124
+ - Puzzle unique ID
125
+ - Solution status
126
+ - Solve time
127
+ - Propagation count
128
+
129
+ Also calculates and stores aggregate statistics per solver:
130
+ - Total puzzles attempted
131
+ - Number of puzzles solved
132
+ - Total and average solving times
133
+ - Total and average propagation counts
134
+ """
135
+ results = {
136
+ solver_name: {
137
+ "puzzles": {},
138
+ "stats": {
139
+ "total_puzzles": 0,
140
+ "solved_count": 0,
141
+ "total_time": 0,
142
+ "total_propagations": 0,
143
+ "avg_time": 0,
144
+ "avg_propagations": 0,
145
+ },
146
+ }
147
+ for solver_name in self.solvers
148
+ }
149
+
150
+ puzzle_files = [f for f in os.listdir(self.puzzles_dir) if f.endswith(".json")]
151
+ print(f"Found {len(puzzle_files)} puzzle files") # Debug
152
+
153
+ for puzzle_file in puzzle_files:
154
+ puzzle_id = puzzle_file[:-5]
155
+ puzzle = self.load_puzzle(puzzle_id)
156
+
157
+ if not puzzle:
158
+ print(f"Failed to load puzzle: {puzzle_id}") # Debug
159
+ continue
160
+
161
+ for solver_name in self.solvers:
162
+ print(f"Running {solver_name} on puzzle {puzzle_id}")
163
+ result = self.run_solver(solver_name, puzzle)
164
+ print(f"Result: {result}") # Debug
165
+
166
+ results[solver_name]["puzzles"][puzzle_id] = result
167
+
168
+ stats = results[solver_name]["stats"]
169
+ stats["total_puzzles"] += 1
170
+ if result["status"] == "sat":
171
+ stats["solved_count"] += 1
172
+ stats["total_time"] += result["solve_time"]
173
+ stats["total_propagations"] += result["propagations"]
174
+
175
+ # Calculate averages
176
+ for solver_name, solver_stats in results.items():
177
+ stats = solver_stats["stats"]
178
+ total_puzzles = stats["total_puzzles"]
179
+ if total_puzzles > 0:
180
+ stats["avg_time"] = stats["total_time"] / total_puzzles
181
+ stats["avg_propagations"] = stats["total_propagations"] / total_puzzles
182
+ print(f"Stats for {solver_name}: {stats}") # Debug
183
+
184
+ # Save results
185
+ timestamp = time.strftime("%Y%m%d_%H%M%S")
186
+
187
+ # Debug CSV data
188
+ csv_data = []
189
+ for solver_name, solver_results in results.items():
190
+ for puzzle_id, puzzle_result in solver_results["puzzles"].items():
191
+ row = {
192
+ "solver": solver_name,
193
+ "puzzle_id": puzzle_id,
194
+ "status": puzzle_result["status"],
195
+ "solve_time": puzzle_result["solve_time"],
196
+ "propagations": puzzle_result["propagations"],
197
+ }
198
+ csv_data.append(row)
199
+ print(f"Adding CSV row: {row}") # Debug
200
+
201
+ csv_path = os.path.join(self.results_dir, f"benchmark_{timestamp}.csv")
202
+ print(f"Writing {len(csv_data)} rows to CSV") # Debug
203
+
204
+ with open(csv_path, "w") as f:
205
+ if csv_data:
206
+ headers = csv_data[0].keys()
207
+ f.write(",".join(headers) + "\n")
208
+ for row in csv_data:
209
+ f.write(",".join(str(row[h]) for h in headers) + "\n")
210
+
211
+ print(f"Benchmark results saved to {csv_path}")
@@ -0,0 +1,4 @@
1
+ from .cvc5_solver import CVC5Solver
2
+ from .dpll_solver import DPLLSolver
3
+ from .dpllt_solver import DPLLTSolver
4
+ from .z3_solver import Z3Solver