sudoku-smt-solvers 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,11 +9,27 @@ Key Components:
9
9
  - Benchmarking suite for comparing solver performance
10
10
  """
11
11
 
12
- from .solvers.dpll_solver import DPLLSolver
13
- from .solvers.dpllt_solver import DPLLTSolver
14
- from .solvers.z3_solver import Z3Solver
15
- from .solvers.cvc5_solver import CVC5Solver
16
- from .benchmarks.benchmark_runner import BenchmarkRunner
17
- from .benchmarks.sudoku_generator.sudoku_generator import SudokuGenerator
12
+ from .solvers import CVC5Solver, DPLLSolver, DPLLTSolver, Z3Solver
13
+ from .solvers.utils import SudokuError
14
+ from .benchmarks import BenchmarkRunner
15
+ from .benchmarks.sudoku_generator import (
16
+ SudokuGenerator,
17
+ LasVegasGenerator,
18
+ HoleDigger,
19
+ DFSSolver,
20
+ )
18
21
 
19
- __version__ = "0.3.0"
22
+ __version__ = "0.4.0"
23
+
24
+ __all__ = [
25
+ "CVC5Solver",
26
+ "DPLLSolver",
27
+ "DPLLTSolver",
28
+ "Z3Solver",
29
+ "BenchmarkRunner",
30
+ "SudokuGenerator",
31
+ "LasVegasGenerator",
32
+ "HoleDigger",
33
+ "DFSSolver",
34
+ "SudokuError",
35
+ ]
@@ -1,5 +1,3 @@
1
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
2
+
3
+ __all__ = ["BenchmarkRunner"]
@@ -0,0 +1,6 @@
1
+ from .sudoku_generator import SudokuGenerator
2
+ from .las_vegas import LasVegasGenerator
3
+ from .hole_digger import HoleDigger
4
+ from .dfs_solver import DFSSolver
5
+
6
+ __all__ = ["SudokuGenerator", "LasVegasGenerator", "HoleDigger", "DFSSolver"]
@@ -0,0 +1,99 @@
1
+ from math import sqrt
2
+
3
+
4
+ class DFSSolver:
5
+ def __init__(self, size=25, solutions_limit=-1):
6
+ """Initialize solver with configurable grid size"""
7
+ self.size = size # Total grid size (default 25x25)
8
+ self.box_size = int(sqrt(size)) # Size of each sub-box (default 5x5)
9
+ self.solutions_limit = solutions_limit # Number of solutions to find (-1 for all solutions, n>0 for n solutions)
10
+ self.solutions_found = 0
11
+ self.rows = [set() for _ in range(self.size)]
12
+ self.cols = [set() for _ in range(self.size)]
13
+ self.boxes = [set() for _ in range(self.size)]
14
+ self.unfilled_positions = [] # Track positions of empty cells
15
+
16
+ self.box_lookup = [[0] * size for _ in range(size)]
17
+ for i in range(size):
18
+ for j in range(size):
19
+ self.box_lookup[i][j] = (i // self.box_size) * self.box_size + (
20
+ j // self.box_size
21
+ )
22
+
23
+ def setup_board(self, grid):
24
+ """Set up initial board state"""
25
+ self.unfilled_positions.clear()
26
+ for i in range(self.size):
27
+ for j in range(self.size):
28
+ num = grid[i][j]
29
+ if num == 0:
30
+ self.unfilled_positions.append((i, j))
31
+ else:
32
+ self.rows[i].add(num)
33
+ self.cols[j].add(num)
34
+ self.boxes[self.box_lookup[i][j]].add(num)
35
+
36
+ def get_valid_numbers(self, row, col):
37
+ """Get valid numbers for a cell using set operations"""
38
+ used = self.rows[row] | self.cols[col] | self.boxes[self.box_lookup[row][col]]
39
+ return set(range(1, self.size + 1)) - used
40
+
41
+ def solve(self, grid):
42
+ """Main solving function using backtracking"""
43
+ self.setup_board(grid)
44
+ self.solutions_found = 0
45
+ solutions = []
46
+
47
+ def search():
48
+ """Depth-first search implementation"""
49
+ if not self.unfilled_positions: # Found a solution
50
+ solutions.append([row[:] for row in grid])
51
+ self.solutions_found += 1
52
+ # Stop if we've found desired number of solutions
53
+ return (
54
+ self.solutions_limit > 0
55
+ and self.solutions_found >= self.solutions_limit
56
+ )
57
+
58
+ # Select cell with minimum valid numbers to reduce branching
59
+ min_candidates = float("inf")
60
+ min_pos = None
61
+ min_idx = None
62
+
63
+ for idx, (row, col) in enumerate(self.unfilled_positions):
64
+ valid_numbers = self.get_valid_numbers(row, col)
65
+ if len(valid_numbers) < min_candidates:
66
+ min_candidates = len(valid_numbers)
67
+ min_pos = (row, col)
68
+ min_idx = idx
69
+ if min_candidates == 0: # No valid numbers available
70
+ return False
71
+
72
+ row, col = min_pos
73
+ self.unfilled_positions.pop(min_idx)
74
+
75
+ for num in self.get_valid_numbers(row, col):
76
+ # Place number and update board state
77
+ grid[row][col] = num
78
+ self.rows[row].add(num)
79
+ self.cols[col].add(num)
80
+ self.boxes[self.box_lookup[row][col]].add(num)
81
+
82
+ if search(): # If search returns True, we should stop
83
+ return True
84
+
85
+ # Backtrack: remove number and restore board state
86
+ grid[row][col] = 0
87
+ self.rows[row].remove(num)
88
+ self.cols[col].remove(num)
89
+ self.boxes[self.box_lookup[row][col]].remove(num)
90
+
91
+ self.unfilled_positions.insert(min_idx, (row, col))
92
+ return False
93
+
94
+ search()
95
+ return (
96
+ solutions
97
+ if self.solutions_limit != 1
98
+ else (solutions[0] if solutions else [])
99
+ )
@@ -0,0 +1,144 @@
1
+ import random
2
+ from typing import List, Tuple
3
+ from .dfs_solver import DFSSolver
4
+
5
+ from .las_vegas import LasVegasGenerator
6
+
7
+ # A mapping from difficulty to the range of givens
8
+ difficulty_givensRange_mapping = {
9
+ "Extremely Easy": [382, float("inf")], # More than 386 given cells
10
+ "Easy": [274, 381],
11
+ "Medium": [243, 273],
12
+ "Difficult": [212, 242],
13
+ "Evil": [166, 211],
14
+ }
15
+
16
+ # A mapping from difficulty to the lower bound of givens in each row and column
17
+ difficulty_lower_bound_mapping = {
18
+ "Extremely Easy": 14,
19
+ "Easy": 11,
20
+ "Medium": 7,
21
+ "Difficult": 4,
22
+ "Evil": 0,
23
+ }
24
+
25
+ digging_sequence_mapping = {
26
+ "Extremely Easy": "random",
27
+ "Easy": "random",
28
+ "Medium": "Jumping one cell",
29
+ "Difficult": "Wandering along 'S'",
30
+ "Evil": "Left to right, top to bottom",
31
+ }
32
+
33
+
34
+ class HoleDigger:
35
+ def __init__(self, puzzle: List[List[int]], difficulty: str):
36
+ self.puzzle = puzzle
37
+ self.size = len(puzzle)
38
+ self.difficulty = difficulty
39
+
40
+ self.target_range = difficulty_givensRange_mapping[difficulty]
41
+ self.num_givens = (
42
+ random.randint(*self.target_range)
43
+ if self.target_range[1] != float("inf")
44
+ else random.randint(self.target_range[0], self.size**2)
45
+ )
46
+
47
+ self.lower_bound = difficulty_lower_bound_mapping[difficulty]
48
+ self.pattern = digging_sequence_mapping[difficulty]
49
+
50
+ self.can_be_dug = {(i, j) for i in range(self.size) for j in range(self.size)}
51
+ self.remaining_cells = self.size * self.size
52
+
53
+ def get_digging_sequence(self) -> List[Tuple[int, int]]:
54
+ if self.pattern == "random":
55
+ return self._random_pattern()
56
+ elif self.pattern == "Jumping one cell":
57
+ return self._jumping_pattern()
58
+ elif self.pattern == "Wandering along 'S'":
59
+ return self._s_pattern()
60
+ else: # "Left to right, top to bottom"
61
+ return self._linear_pattern()
62
+
63
+ def _random_pattern(self) -> List[Tuple[int, int]]:
64
+ cells = list(self.can_be_dug)
65
+ random.shuffle(cells)
66
+ return cells
67
+
68
+ def _jumping_pattern(self) -> List[Tuple[int, int]]:
69
+ sequence = []
70
+ # Forward pass - only on even rows (0, 2, 4...)
71
+ for i in range(0, self.size, 2):
72
+ for j in range(0, self.size, 2):
73
+ sequence.append((i, j))
74
+
75
+ # Backward pass - only on odd rows (1, 3, 5...)
76
+ for i in range(1, self.size, 2):
77
+ for j in range(self.size - 2, -1, -2):
78
+ sequence.append((i, j))
79
+
80
+ return sequence
81
+
82
+ def _s_pattern(self) -> List[Tuple[int, int]]:
83
+ sequence = []
84
+ for i in range(self.size):
85
+ row = range(self.size) if i % 2 == 0 else range(self.size - 1, -1, -1)
86
+ for j in row:
87
+ sequence.append((i, j))
88
+ return sequence
89
+
90
+ def _linear_pattern(self) -> List[Tuple[int, int]]:
91
+ return [(i, j) for i in range(self.size) for j in range(self.size)]
92
+
93
+ def pass_restrictions(self, row: int, col: int) -> bool:
94
+ # Skip if already dug
95
+ if self.puzzle[row][col] == 0:
96
+ return False
97
+
98
+ # Check if digging would violate minimum remaining cells
99
+ if self.remaining_cells - 1 < self.num_givens:
100
+ return False
101
+
102
+ # Check row constraint
103
+ row_remaining = sum(cell != 0 for cell in self.puzzle[row])
104
+ if row_remaining - 1 < self.lower_bound:
105
+ return False
106
+
107
+ # Check column constraint
108
+ col_remaining = sum(self.puzzle[i][col] != 0 for i in range(self.size))
109
+ if col_remaining - 1 < self.lower_bound:
110
+ return False
111
+
112
+ return True
113
+
114
+ def has_unique_solution(self) -> bool:
115
+ solver = DFSSolver(size=self.size, solutions_limit=2)
116
+ solutions = solver.solve(self.puzzle)
117
+ return len(solutions) == 1
118
+
119
+ def dig_holes(self) -> List[List[int]]:
120
+ digging_sequence = self.get_digging_sequence()
121
+ cells_to_check = set(digging_sequence)
122
+
123
+ while cells_to_check:
124
+ row, col = next(iter(cells_to_check))
125
+
126
+ if not self.pass_restrictions(row, col):
127
+ cells_to_check.remove((row, col))
128
+ continue
129
+
130
+ # Save current value in case we need to restore it
131
+ current_value = self.puzzle[row][col]
132
+ self.puzzle[row][col] = 0
133
+
134
+ if not self.has_unique_solution():
135
+ # Restore the value if digging creates multiple solutions
136
+ self.puzzle[row][col] = current_value
137
+ self.can_be_dug.remove((row, col))
138
+ else:
139
+ # Update remaining cells if digging is successful
140
+ self.remaining_cells -= 1
141
+
142
+ cells_to_check.remove((row, col))
143
+
144
+ return self.puzzle
@@ -0,0 +1,150 @@
1
+ import random
2
+ import time
3
+ from .dfs_solver import DFSSolver
4
+ from typing import List, Set, Tuple
5
+ from multiprocessing import Process, Queue
6
+
7
+
8
+ def solve_with_timeout(grid, solver, queue):
9
+ start_time = time.time()
10
+ solutions = solver.solve(grid)
11
+ solve_time = time.time() - start_time
12
+ queue.put((solutions, solve_time))
13
+
14
+
15
+ class LasVegasGenerator:
16
+ def __init__(self, size: int = 25, givens: int = 80, timeout: int | None = 5):
17
+ self.size = size
18
+ self.givens = givens
19
+ self.timeout = timeout
20
+ self.all_positions = [
21
+ (i, j) for i in range(self.size) for j in range(self.size)
22
+ ] # Create list of all possible positions
23
+ self.rows = [set() for _ in range(self.size)]
24
+ self.cols = [set() for _ in range(self.size)]
25
+ self.boxes = [set() for _ in range(self.size)]
26
+ self.solver = DFSSolver(size, solutions_limit=1)
27
+
28
+ box_size = int(self.size**0.5)
29
+ self.box_lookup = [[0] * size for _ in range(size)]
30
+ for i in range(size):
31
+ for j in range(size):
32
+ self.box_lookup[i][j] = (i // box_size) * box_size + (j // box_size)
33
+
34
+ def create_empty_grid(self) -> List[List[int]]:
35
+ return [[0 for _ in range(self.size)] for _ in range(self.size)]
36
+
37
+ def get_random_positions(self) -> Set[Tuple[int, int]]:
38
+ # Use random.sample to select givens number of positions without replacement
39
+ selected_positions = random.sample(self.all_positions, self.givens)
40
+
41
+ return set(selected_positions)
42
+
43
+ def is_valid_number(
44
+ self, grid: List[List[int]], row: int, col: int, num: int
45
+ ) -> bool:
46
+ # Check if number exists in row, column or box using sets
47
+ if (
48
+ num in self.rows[row]
49
+ or num in self.cols[col]
50
+ or num in self.boxes[self.box_lookup[row][col]]
51
+ ):
52
+ return False
53
+ return True
54
+
55
+ def fill_random_positions(
56
+ self, grid: List[List[int]], positions: Set[Tuple[int, int]]
57
+ ) -> bool:
58
+ # Clear existing sets
59
+ self.rows = [set() for _ in range(self.size)]
60
+ self.cols = [set() for _ in range(self.size)]
61
+ self.boxes = [set() for _ in range(self.size)]
62
+
63
+ positions_list = list(positions)
64
+ random.shuffle(positions_list)
65
+
66
+ def backtrack(pos_index: int) -> bool:
67
+ if pos_index == len(positions_list):
68
+ return True
69
+
70
+ row, col = positions_list[pos_index]
71
+ valid_numbers = [
72
+ num
73
+ for num in range(1, self.size + 1)
74
+ if self.is_valid_number(grid, row, col, num)
75
+ ]
76
+
77
+ random.shuffle(valid_numbers)
78
+
79
+ for num in valid_numbers:
80
+ grid[row][col] = num
81
+ self.rows[row].add(num)
82
+ self.cols[col].add(num)
83
+ self.boxes[self.box_lookup[row][col]].add(num)
84
+
85
+ if backtrack(pos_index + 1):
86
+ return True
87
+
88
+ grid[row][col] = 0
89
+ self.rows[row].remove(num)
90
+ self.cols[col].remove(num)
91
+ self.boxes[self.box_lookup[row][col]].remove(num)
92
+
93
+ return False
94
+
95
+ return backtrack(0)
96
+
97
+ def generate(self) -> List[List[int]]:
98
+ attempts = 0
99
+
100
+ while True:
101
+ attempts += 1
102
+ print(f"Attempt {attempts}...")
103
+ attempt_start = time.time()
104
+
105
+ grid = self.create_empty_grid()
106
+ positions = self.get_random_positions()
107
+
108
+ if not self.fill_random_positions(grid, positions):
109
+ continue
110
+
111
+ queue = Queue()
112
+ process = Process(
113
+ target=solve_with_timeout, args=(grid, self.solver, queue)
114
+ )
115
+ process.start()
116
+
117
+ if self.timeout is not None:
118
+ process.join(timeout=self.timeout)
119
+ if process.is_alive():
120
+ process.terminate()
121
+ process.join()
122
+ continue
123
+ else:
124
+ process.join()
125
+ attempt_duration = time.time() - attempt_start
126
+ print(f"Attempt duration: {attempt_duration:.2f} seconds")
127
+
128
+ try:
129
+ solution, solve_time = queue.get_nowait()
130
+ if solution:
131
+ print(f"Found valid puzzle after {attempts} attempts")
132
+ print(f"Solving time: {solve_time:.2f} seconds")
133
+ return solution
134
+ except Exception: # Changed from queue.Empty to catch any queue errors
135
+ continue
136
+
137
+
138
+ def print_grid(grid: List[List[int]]):
139
+ size = len(grid)
140
+ box_size = int(size**0.5)
141
+
142
+ for i, row in enumerate(grid):
143
+ if i > 0 and i % box_size == 0:
144
+ print("-" * (size * 3 + box_size))
145
+
146
+ for j, num in enumerate(row):
147
+ if j > 0 and j % box_size == 0:
148
+ print("|", end=" ")
149
+ print(f"{num:2}", end=" ")
150
+ print()
@@ -0,0 +1,113 @@
1
+ import os
2
+ import json
3
+ import copy
4
+ from datetime import datetime
5
+ from typing import List, Tuple
6
+
7
+ from .las_vegas import LasVegasGenerator
8
+ from .hole_digger import HoleDigger
9
+
10
+
11
+ class SudokuGenerator:
12
+ """Generates Sudoku puzzles and their solutions using Las Vegas algorithm.
13
+
14
+ This class handles the complete puzzle generation process:
15
+ 1. Generates a complete valid solution using Las Vegas algorithm
16
+ 2. Creates holes in the solution to create the actual puzzle
17
+ 3. Saves both puzzle and solution with metadata
18
+
19
+ Attributes:
20
+ size (int): Size of the Sudoku grid (e.g., 25 for 25x25)
21
+ givens (int): Number of initial filled positions for Las Vegas
22
+ timeout (int): Maximum generation attempt time in seconds
23
+ difficulty (str): Target difficulty level for hole creation
24
+ puzzles_dir (str): Directory for storing generated puzzles
25
+ solutions_dir (str): Directory for storing solutions
26
+
27
+ Example:
28
+ >>> generator = SudokuGenerator(size=9, difficulty="Hard")
29
+ >>> puzzle, solution, puzzle_id = generator.generate()
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ size: int = 25,
35
+ givens: int = 80,
36
+ timeout: int = 5,
37
+ difficulty: str = "Medium",
38
+ puzzles_dir: str = "benchmarks/puzzles",
39
+ solutions_dir: str = "benchmarks/solutions",
40
+ ):
41
+ """Initialize the Sudoku puzzle generator.
42
+
43
+ Args:
44
+ size: Grid size (default 25 for 25x25 grid)
45
+ givens: Number of initial filled positions for Las Vegas
46
+ timeout: Maximum time in seconds for generation attempts
47
+ difficulty: Puzzle difficulty level for hole digger
48
+ puzzles_dir: Directory to store generated puzzles
49
+ solutions_dir: Directory to store solutions
50
+ """
51
+ self.size = size
52
+ self.givens = givens
53
+ self.timeout = timeout
54
+ self.difficulty = difficulty
55
+ self.puzzles_dir = puzzles_dir
56
+ self.solutions_dir = solutions_dir
57
+
58
+ # Create directories if they don't exist
59
+ os.makedirs(puzzles_dir, exist_ok=True)
60
+ os.makedirs(solutions_dir, exist_ok=True)
61
+
62
+ def generate(self) -> Tuple[List[List[int]], List[List[int]], str]:
63
+ """Generate a complete Sudoku puzzle and solution pair.
64
+
65
+ Uses a two-step process:
66
+ 1. Las Vegas algorithm generates a complete valid solution
67
+ 2. Hole digger creates the puzzle by removing certain cells
68
+
69
+ Returns:
70
+ tuple: Contains:
71
+ - List[List[int]]: The puzzle grid with holes
72
+ - List[List[int]]: The complete solution grid
73
+ - str: Unique identifier for the puzzle/solution pair
74
+
75
+ Note:
76
+ The generated puzzle is guaranteed to have a unique solution
77
+ """
78
+ # Step 1: Generate complete solution using Las Vegas
79
+ generator = LasVegasGenerator(self.size, self.givens, self.timeout)
80
+ solution = generator.generate()
81
+
82
+ # Step 2: Create holes using HoleDigger
83
+ digger = HoleDigger(copy.deepcopy(solution), self.difficulty)
84
+ puzzle = digger.dig_holes()
85
+
86
+ # Generate unique identifier for this puzzle
87
+ puzzle_id = self._generate_puzzle_id()
88
+
89
+ # Save both puzzle and solution
90
+ self._save_grid(puzzle, puzzle_id, is_puzzle=True)
91
+ self._save_grid(solution, puzzle_id, is_puzzle=False)
92
+
93
+ return puzzle, solution, puzzle_id
94
+
95
+ def _generate_puzzle_id(self) -> str:
96
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
97
+ return f"sudoku_{self.size}x{self.size}_{self.difficulty}_{timestamp}"
98
+
99
+ def _save_grid(self, grid: List[List[int]], puzzle_id: str, is_puzzle: bool):
100
+ directory = self.puzzles_dir if is_puzzle else self.solutions_dir
101
+ filename = f"{puzzle_id}_{'puzzle' if is_puzzle else 'solution'}.json"
102
+ filepath = os.path.join(directory, filename)
103
+
104
+ metadata = {
105
+ "size": self.size,
106
+ "difficulty": self.difficulty,
107
+ "givens": sum(cell != 0 for row in grid for cell in row),
108
+ "type": "puzzle" if is_puzzle else "solution",
109
+ "grid": grid,
110
+ }
111
+
112
+ with open(filepath, "w") as f:
113
+ json.dump(metadata, f, indent=2)
@@ -2,4 +2,5 @@ from .cvc5_solver import CVC5Solver
2
2
  from .dpll_solver import DPLLSolver
3
3
  from .dpllt_solver import DPLLTSolver
4
4
  from .z3_solver import Z3Solver
5
- from .utils.sudoku_error import SudokuError
5
+
6
+ __all__ = ["CVC5Solver", "DPLLSolver", "DPLLTSolver", "Z3Solver"]
@@ -0,0 +1,3 @@
1
+ from .sudoku_error import SudokuError
2
+
3
+ __all__ = ["SudokuError"]
@@ -0,0 +1,8 @@
1
+ class SudokuError(Exception):
2
+ """An exception class for errors in the sudoku solver"""
3
+
4
+ def __init__(self, message):
5
+ self.message = message
6
+
7
+ def __str__(self):
8
+ return self.message
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: sudoku_smt_solvers
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A collection of SAT and SMT solvers for solving Sudoku puzzles
5
5
  Home-page: https://liamjdavis.github.io/sudoku-smt-solvers
6
6
  Author: Liam Davis, Tairan 'Ryan' Ji
@@ -61,7 +61,7 @@ This package includes the DPLL solver and three modern SMT solvers:
61
61
  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:
62
62
 
63
63
  ```python
64
- from sudoku_smt_solvers.solvers.z3_solver import Z3Solver
64
+ from sudoku_smt_solvers import Z3Solver
65
65
 
66
66
  # Example grid (25x25)
67
67
  grid = [[0] * 25 for _ in range(25)]
@@ -78,7 +78,7 @@ else:
78
78
  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:
79
79
 
80
80
  ```python
81
- from sudoku_smt_solvers.benchmarks.sudoku_generator.sudoku_generator import SudokuGenerator
81
+ from sudoku_smt_solvers import SudokuGenerator
82
82
 
83
83
  generator = SudokuGenerator(size = 25, givens = 80, timeout = 5, difficulty = "Medium", puzzles_dir = "benchmarks/puzzles", solutions_dir = "benchmarks/solutions")
84
84
 
@@ -91,7 +91,7 @@ Due to the computational complexity of generating large sudoku puzzles, it is re
91
91
  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:
92
92
 
93
93
  ```python
94
- from sudoku_smt_solvers.benchmarks.benchmark_runner import BenchmarkRunner
94
+ from sudoku_smt_solvers import BenchmarkRunner
95
95
 
96
96
  runner = BenchmarkRunner(
97
97
  puzzles_dir='resources/benchmarks/puzzles/',
@@ -0,0 +1,20 @@
1
+ sudoku_smt_solvers/__init__.py,sha256=PJo8qJmamMhps_-HTKegaxq3FqUf_PWWz7f_8DqRafk,935
2
+ sudoku_smt_solvers/benchmarks/__init__.py,sha256=qRc3eFgeV0yzO2NgSnlqjJ3ejHgDxlI1ySId6WspT24,77
3
+ sudoku_smt_solvers/benchmarks/benchmark_runner.py,sha256=Mc87ul-6VkWMomtlmOMc9GXmC4AwfQUwIWKDjeFvSVA,7712
4
+ sudoku_smt_solvers/benchmarks/sudoku_generator/__init__.py,sha256=Ob4Qt1GyqixpvZceGZxyYWd1doefukN_WnHns1SBDQg,236
5
+ sudoku_smt_solvers/benchmarks/sudoku_generator/dfs_solver.py,sha256=rkuCs0frivbzLg0pF5YZvi2rfVW9qRr5vtcJxvY9Crw,3890
6
+ sudoku_smt_solvers/benchmarks/sudoku_generator/hole_digger.py,sha256=yjjl9J_aeBwSVa4cc8VDMm-A7KMo2_5292zxzLaLzmQ,4793
7
+ sudoku_smt_solvers/benchmarks/sudoku_generator/las_vegas.py,sha256=koXte_GjftrbZEvKscE09VcXme1i-l4YsWcjvzIYJ8k,4999
8
+ sudoku_smt_solvers/benchmarks/sudoku_generator/sudoku_generator.py,sha256=pPZw6szPvMfnoydjcfYZu87-6jU5SVgmVwhGo3Ir5qw,4258
9
+ sudoku_smt_solvers/solvers/__init__.py,sha256=5QRpexW7hj4nBVJnWdJzdOwh2T2iheLLgIlAYKUozvg,209
10
+ sudoku_smt_solvers/solvers/cvc5_solver.py,sha256=igN27LHxUADmYw3farmEeGtjJEsrdSQEk0X0X_EItyQ,6855
11
+ sudoku_smt_solvers/solvers/dpll_solver.py,sha256=itLEx8QdomSS9nA7CrhSlU7wN-e8vDInAWiwhXX-7Ug,6144
12
+ sudoku_smt_solvers/solvers/dpllt_solver.py,sha256=8HJWOGU6Oj4hNpgkK96sI51bdiTfRLSlf5yhic7WeF0,7992
13
+ sudoku_smt_solvers/solvers/z3_solver.py,sha256=e6WPwPdN8uUipjxNm7KRYlbsNNwGyxH3s4wy5wC8PPc,4902
14
+ sudoku_smt_solvers/solvers/utils/__init__.py,sha256=TPg_Q5aZs_QOC9CEesZhf9o7uUC3c7oG_vP5nBcyc2M,65
15
+ sudoku_smt_solvers/solvers/utils/sudoku_error.py,sha256=iUcv1QCgQ7anov0b-AtIB1fbfZ3yWfci4eTp_8RuUJg,208
16
+ sudoku_smt_solvers-0.4.0.dist-info/LICENSE,sha256=PbuZlvluV1l4HMMfPAVe5yjVvFGBK9DFp20JNhoJ8bI,1067
17
+ sudoku_smt_solvers-0.4.0.dist-info/METADATA,sha256=4BGL_GK0m0dTFzKJRqYFnN0vXzELS8sXSgu6qNxL2Dw,6589
18
+ sudoku_smt_solvers-0.4.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
19
+ sudoku_smt_solvers-0.4.0.dist-info/top_level.txt,sha256=Ww9vs8KC4aujzfGfddMl_X8Qzh-Cywn9aBTLQgemi5A,19
20
+ sudoku_smt_solvers-0.4.0.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- sudoku_smt_solvers/__init__.py,sha256=HlyADLi9blzeaktg33Xn-8nl-jEXfF-lxu2eYZ2DirQ,763
2
- sudoku_smt_solvers/benchmarks/__init__.py,sha256=lMrK_yj_otywN4dMvvVFtTzyTdamS_4nUgjm-k07obU,271
3
- sudoku_smt_solvers/benchmarks/benchmark_runner.py,sha256=Mc87ul-6VkWMomtlmOMc9GXmC4AwfQUwIWKDjeFvSVA,7712
4
- sudoku_smt_solvers/solvers/__init__.py,sha256=QDJp4TOJp1ipYwyXomHRkIxuqO_p-Vpr9zGwJQFiEOk,186
5
- sudoku_smt_solvers/solvers/cvc5_solver.py,sha256=igN27LHxUADmYw3farmEeGtjJEsrdSQEk0X0X_EItyQ,6855
6
- sudoku_smt_solvers/solvers/dpll_solver.py,sha256=itLEx8QdomSS9nA7CrhSlU7wN-e8vDInAWiwhXX-7Ug,6144
7
- sudoku_smt_solvers/solvers/dpllt_solver.py,sha256=8HJWOGU6Oj4hNpgkK96sI51bdiTfRLSlf5yhic7WeF0,7992
8
- sudoku_smt_solvers/solvers/z3_solver.py,sha256=e6WPwPdN8uUipjxNm7KRYlbsNNwGyxH3s4wy5wC8PPc,4902
9
- sudoku_smt_solvers-0.3.0.dist-info/LICENSE,sha256=PbuZlvluV1l4HMMfPAVe5yjVvFGBK9DFp20JNhoJ8bI,1067
10
- sudoku_smt_solvers-0.3.0.dist-info/METADATA,sha256=8gTnQaFcWAhdZnHc0Gqtbgyu7kf9hOPbx4zkCB2Mly0,6680
11
- sudoku_smt_solvers-0.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
12
- sudoku_smt_solvers-0.3.0.dist-info/top_level.txt,sha256=Ww9vs8KC4aujzfGfddMl_X8Qzh-Cywn9aBTLQgemi5A,19
13
- sudoku_smt_solvers-0.3.0.dist-info/RECORD,,