From f131e50da7c9624f8445303d258d8f5dd895c8e0 Mon Sep 17 00:00:00 2001 From: Anthony Ling Date: Tue, 8 Dec 2020 21:18:12 -0800 Subject: [PATCH 1/3] Updated GeneralSolver to handle common solver errors --- puzzlesolver/solvers/generalsolver.py | 9 ++++- tests/conftest.py | 25 ++----------- tests/puzzles/test_Hanoi.py | 4 +- tests/solvers/test_GeneralSolver.py | 54 ++++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 25 deletions(-) diff --git a/puzzlesolver/solvers/generalsolver.py b/puzzlesolver/solvers/generalsolver.py index ceb53ed..fd3bd96 100644 --- a/puzzlesolver/solvers/generalsolver.py +++ b/puzzlesolver/solvers/generalsolver.py @@ -1,5 +1,5 @@ from .solver import Solver -from ..util import * +from ..util import PuzzleValue, PuzzleException import queue as q import progressbar @@ -27,6 +27,10 @@ def solve(self, *args, verbose=False, **kwargs): else: # Not a CSP - use generateSolutions() for solution in solutions: + # Check if all the solutions are SOLVABLE + assert solution.primitive() == PuzzleValue.SOLVABLE, """ + `generateSolutions` contains an UNSOLVABLE position + """ self.remoteness[hash(solution)] = 0 queue.put(solution) @@ -43,6 +47,9 @@ def solve(self, *args, verbose=False, **kwargs): for move in puzzle.generateMoves('undo'): nextPuzzle = puzzle.doMove(move) if hash(nextPuzzle) not in self.remoteness: + assert nextPuzzle.primitive() != PuzzleValue.SOLVABLE, """ + Found a state where primitive was SOLVABLE while traversing Puzzle tree + """ self.remoteness[hash(nextPuzzle)] = self.remoteness[hash(puzzle)] + 1 queue.put(nextPuzzle) if verbose: bar.finish() diff --git a/tests/conftest.py b/tests/conftest.py index b42731b..2c8af6e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,10 @@ from puzzlesolver.util import PuzzleValue from puzzlesolver.puzzles import puzzleList, GraphPuzzle +######################################################################## +# Server Fixtures +######################################################################## + @pytest.fixture def client(tmpdir): app = server.app @@ -16,24 +20,3 @@ def client(tmpdir): with app.test_client() as client: yield client - -@pytest.fixture -def simple(): - def helper(solver_cls, csp=False): - forward = GraphPuzzle(0, csp=csp) - bidirectional = GraphPuzzle(1, csp=csp) - backward = GraphPuzzle(2, csp=csp) - sol = GraphPuzzle(3, value=PuzzleValue.SOLVABLE, csp=csp) - - sol.setMove(forward, movetype="for") - sol.setMove(bidirectional, movetype="bi") - sol.setMove(backward, movetype="back") - - solver = solver_cls(sol) - solver.solve() - - assert solver.getRemoteness(backward) == 1 - assert solver.getRemoteness(sol) == 0 - assert solver.getRemoteness(bidirectional) == 1 - assert solver.getRemoteness(forward) == PuzzleValue.UNSOLVABLE - return helper diff --git a/tests/puzzles/test_Hanoi.py b/tests/puzzles/test_Hanoi.py index 9d734e7..530238c 100644 --- a/tests/puzzles/test_Hanoi.py +++ b/tests/puzzles/test_Hanoi.py @@ -3,7 +3,7 @@ from puzzlesolver.puzzles import Hanoi from puzzlesolver.solvers import GeneralSolver -from puzzlesolver.util import * +from puzzlesolver.util import PuzzleValue, PuzzleException def move(move0, move1): return (move0, move1) @@ -13,7 +13,7 @@ def testHash(): """Tests the expected behavior of the hash function on the puzzle states. """ puzzle0 = Hanoi.deserialize('3_2_1--') puzzle1 = Hanoi.deserialize('3_2_1--') - puzzle2 = Hanoi.deserialize('-3_2_1-') + #puzzle2 = Hanoi.deserialize('-3_2_1-') puzzle3 = Hanoi.deserialize('--3_2_1') # Checks if two of the exact same states have the same hash diff --git a/tests/solvers/test_GeneralSolver.py b/tests/solvers/test_GeneralSolver.py index f2155ef..6e75e50 100644 --- a/tests/solvers/test_GeneralSolver.py +++ b/tests/solvers/test_GeneralSolver.py @@ -2,8 +2,60 @@ from puzzlesolver.puzzles import GraphPuzzle from puzzlesolver.solvers import GeneralSolver -from puzzlesolver.util import * +from puzzlesolver.util import PuzzleValue + +######################################################################## +# Helper Functions +######################################################################## + +def simple(solver_cls, csp=False): + forward = GraphPuzzle(0, csp=csp) + bidirectional = GraphPuzzle(1, csp=csp) + backward = GraphPuzzle(2, csp=csp) + sol = GraphPuzzle(3, value=PuzzleValue.SOLVABLE, csp=csp) + + sol.setMove(forward, movetype="for") + sol.setMove(bidirectional, movetype="bi") + sol.setMove(backward, movetype="back") + + solver = solver_cls(sol) + solver.solve() + + assert solver.getRemoteness(backward) == 1 + assert solver.getRemoteness(sol) == 0 + assert solver.getRemoteness(bidirectional) == 1 + assert solver.getRemoteness(forward) == PuzzleValue.UNSOLVABLE + +######################################################################## +# Tests +######################################################################## def testSimple(simple): simple(GeneralSolver, csp=False) simple(GeneralSolver, csp=True) + +def testSolutionsError(): + record_sol = GraphPuzzle(0, value=PuzzleValue.SOLVABLE) + missed_sol = GraphPuzzle(1, value=PuzzleValue.SOLVABLE) + not_a_solu = GraphPuzzle(2) + + record_sol.setMove(missed_sol, movetype="bi") + record_sol.setMove(not_a_solu, movetype="bi") + + # Two solutions, but only one is called in generateSolutions + record_sol.solutions.remove(missed_sol) + + try: + solver = GeneralSolver(record_sol) + solver.solve() + except AssertionError: + pass + + # generateSolutions also includes a state that is not a solution + record_sol.solutions.update(set([record_sol, missed_sol, not_a_solu])) + + try: + solver = GeneralSolver(record_sol) + solver.solve() + except AssertionError: + pass \ No newline at end of file From c38f58d7c731630cc8b810362d51455bbdca07ac Mon Sep 17 00:00:00 2001 From: Anthony Ling Date: Wed, 9 Dec 2020 18:31:59 -0800 Subject: [PATCH 2/3] Removed optional args and kwargs parameters from GeneralSolver to remove confusion --- puzzlesolver/solvers/generalsolver.py | 6 +++--- puzzlesolver/solvers/indexsolver.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/puzzlesolver/solvers/generalsolver.py b/puzzlesolver/solvers/generalsolver.py index fd3bd96..2a6f5e0 100644 --- a/puzzlesolver/solvers/generalsolver.py +++ b/puzzlesolver/solvers/generalsolver.py @@ -5,17 +5,17 @@ class GeneralSolver(Solver): - def __init__(self, puzzle, *args, **kwarg): + def __init__(self, puzzle): self.remoteness = {} self.puzzle = puzzle - def getRemoteness(self, puzzle, **kwargs): + def getRemoteness(self, puzzle): """Returns remoteness of puzzle. Automatically solves if memory isn't set""" if not self.remoteness: print("Warning: No memory found. Please make sure that `solve` was called.") if hash(puzzle) in self.remoteness: return self.remoteness[hash(puzzle)] return PuzzleValue.UNSOLVABLE - def solve(self, *args, verbose=False, **kwargs): + def solve(self, verbose=False): """Traverse the entire puzzle tree and classifies all the positions with values and remoteness """ diff --git a/puzzlesolver/solvers/indexsolver.py b/puzzlesolver/solvers/indexsolver.py index 9a9e471..b134ba2 100644 --- a/puzzlesolver/solvers/indexsolver.py +++ b/puzzlesolver/solvers/indexsolver.py @@ -12,9 +12,9 @@ class IndexSolver(GeneralSolver): the index of the chunk. Recommended for puzzles with tight hash functions. """ def __init__(self, puzzle, *args, dir_path='databases', **kwargs): + GeneralSolver.__init__(self, puzzle, *args, **kwargs) if not os.path.exists(dir_path): os.makedirs(dir_path) self.path = '{}/{}.txt'.format(dir_path, puzzle.getName()) - GeneralSolver.__init__(self, puzzle, *args, **kwargs) def getRemoteness(self, puzzle, *args, **kwargs): if hash(puzzle) in self.remoteness: return self.remoteness[hash(puzzle)] From ebd3a8379c7a6cbfc3dbed03ffc042d10abc4480 Mon Sep 17 00:00:00 2001 From: Anthony Ling Date: Tue, 22 Dec 2020 21:22:56 -0800 Subject: [PATCH 3/3] Reverted changes done to tests, too radical for now --- tests/conftest.py | 21 +++++++++++++++++++++ tests/solvers/test_GeneralSolver.py | 22 ---------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2c8af6e..b801850 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,3 +20,24 @@ def client(tmpdir): with app.test_client() as client: yield client + +@pytest.fixture +def simple(): + def helper(solver_cls, csp=False): + forward = GraphPuzzle(0, csp=csp) + bidirectional = GraphPuzzle(1, csp=csp) + backward = GraphPuzzle(2, csp=csp) + sol = GraphPuzzle(3, value=PuzzleValue.SOLVABLE, csp=csp) + + sol.setMove(forward, movetype="for") + sol.setMove(bidirectional, movetype="bi") + sol.setMove(backward, movetype="back") + + solver = solver_cls(sol) + solver.solve() + + assert solver.getRemoteness(backward) == 1 + assert solver.getRemoteness(sol) == 0 + assert solver.getRemoteness(bidirectional) == 1 + assert solver.getRemoteness(forward) == PuzzleValue.UNSOLVABLE + return helper diff --git a/tests/solvers/test_GeneralSolver.py b/tests/solvers/test_GeneralSolver.py index 6e75e50..86d0faf 100644 --- a/tests/solvers/test_GeneralSolver.py +++ b/tests/solvers/test_GeneralSolver.py @@ -4,28 +4,6 @@ from puzzlesolver.solvers import GeneralSolver from puzzlesolver.util import PuzzleValue -######################################################################## -# Helper Functions -######################################################################## - -def simple(solver_cls, csp=False): - forward = GraphPuzzle(0, csp=csp) - bidirectional = GraphPuzzle(1, csp=csp) - backward = GraphPuzzle(2, csp=csp) - sol = GraphPuzzle(3, value=PuzzleValue.SOLVABLE, csp=csp) - - sol.setMove(forward, movetype="for") - sol.setMove(bidirectional, movetype="bi") - sol.setMove(backward, movetype="back") - - solver = solver_cls(sol) - solver.solve() - - assert solver.getRemoteness(backward) == 1 - assert solver.getRemoteness(sol) == 0 - assert solver.getRemoteness(bidirectional) == 1 - assert solver.getRemoteness(forward) == PuzzleValue.UNSOLVABLE - ######################################################################## # Tests ########################################################################