From fe514c62ba7672c74d585a2dfce84c944ddf0318 Mon Sep 17 00:00:00 2001 From: Donato Date: Sun, 24 Mar 2019 17:03:23 +0100 Subject: [PATCH 01/21] changed queue to set in AC3 Changed queue to set in AC3 (as in the pseudocode of the original algorithm) to reduce the number of consistency-check due to the redundancy of the same arcs in queue. For example, on the harder1 configuration of the Sudoku CSP the number consistency-check has been reduced from 40464 to 12562! --- csp.py | 8 ++++---- tests/test_csp.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/csp.py b/csp.py index d5f96f80b..ee59d4a6b 100644 --- a/csp.py +++ b/csp.py @@ -160,7 +160,7 @@ def conflicted_vars(self, current): def AC3(csp, queue=None, removals=None): """[Figure 6.3]""" if queue is None: - queue = [(Xi, Xk) for Xi in csp.variables for Xk in csp.neighbors[Xi]] + queue = {(Xi, Xk) for Xi in csp.variables for Xk in csp.neighbors[Xi]} csp.support_pruning() while queue: (Xi, Xj) = queue.pop() @@ -169,7 +169,7 @@ def AC3(csp, queue=None, removals=None): return False for Xk in csp.neighbors[Xi]: if Xk != Xj: - queue.append((Xk, Xi)) + queue.add((Xk, Xi)) return True @@ -243,7 +243,7 @@ def forward_checking(csp, var, value, assignment, removals): def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" - return AC3(csp, [(X, var) for X in csp.neighbors[var]], removals) + return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) # The search, proper @@ -374,7 +374,7 @@ def make_arc_consistent(Xj, Xk, csp): # Found a consistent assignment for val1, keep it keep = True break - + if not keep: # Remove val1 csp.prune(Xj, val1, None) diff --git a/tests/test_csp.py b/tests/test_csp.py index 2bc907b6c..94660c853 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -13,7 +13,7 @@ def test_csp_assign(): assignment = {} australia.assign(var, val, assignment) - assert australia.nassigns == 1 + # assert australia.nassigns == 1 assert assignment[var] == val @@ -210,7 +210,7 @@ def test_AC3(): assert AC3(csp, removals=removals) is True assert (removals == [('A', 1), ('A', 3), ('B', 1), ('B', 3)] or removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) - + domains = {'A': [ 2, 4], 'B': [ 3, 5]} constraints = lambda X, x, Y, y: int(x) > int (y) removals=[] From 0129aa988d22b592fdb45352c414a1f8bc9576c5 Mon Sep 17 00:00:00 2001 From: Donato Date: Thu, 28 Mar 2019 13:43:04 +0100 Subject: [PATCH 02/21] re-added test commented by mistake --- tests/test_csp.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_csp.py b/tests/test_csp.py index 94660c853..77b35c796 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -3,7 +3,6 @@ from csp import * import random - random.seed("aima-python") @@ -13,7 +12,7 @@ def test_csp_assign(): assignment = {} australia.assign(var, val, assignment) - # assert australia.nassigns == 1 + assert australia.nassigns == 1 assert assignment[var] == val @@ -174,7 +173,7 @@ def test_csp_conflicted_vars(): def test_revise(): neighbors = parse_neighbors('A: B; B: ') domains = {'A': [0], 'B': [4]} - constraints = lambda X, x, Y, y: x % 2 == 0 and (x+y) == 4 + constraints = lambda X, x, Y, y: x % 2 == 0 and (x + y) == 4 csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) csp.support_pruning() @@ -196,14 +195,14 @@ def test_revise(): def test_AC3(): neighbors = parse_neighbors('A: B; B: ') domains = {'A': [0, 1, 2, 3, 4], 'B': [0, 1, 2, 3, 4]} - constraints = lambda X, x, Y, y: x % 2 == 0 and (x+y) == 4 and y % 2 != 0 + constraints = lambda X, x, Y, y: x % 2 == 0 and (x + y) == 4 and y % 2 != 0 removals = [] csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) assert AC3(csp, removals=removals) is False - constraints = lambda X, x, Y, y: (x % 2) == 0 and (x+y) == 4 + constraints = lambda X, x, Y, y: (x % 2) == 0 and (x + y) == 4 removals = [] csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) @@ -211,9 +210,9 @@ def test_AC3(): assert (removals == [('A', 1), ('A', 3), ('B', 1), ('B', 3)] or removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) - domains = {'A': [ 2, 4], 'B': [ 3, 5]} - constraints = lambda X, x, Y, y: int(x) > int (y) - removals=[] + domains = {'A': [2, 4], 'B': [3, 5]} + constraints = lambda X, x, Y, y: int(x) > int(y) + removals = [] csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) assert AC3(csp, removals=removals) @@ -247,7 +246,7 @@ def test_num_legal_values(): def test_mrv(): neighbors = parse_neighbors('A: B; B: C; C: ') domains = {'A': [0, 1, 2, 3, 4], 'B': [4], 'C': [0, 1, 2, 3, 4]} - constraints = lambda X, x, Y, y: x % 2 == 0 and (x+y) == 4 + constraints = lambda X, x, Y, y: x % 2 == 0 and (x + y) == 4 csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) assignment = {'A': 0} @@ -269,13 +268,13 @@ def test_mrv(): def test_unordered_domain_values(): map_coloring_test = MapColoringCSP(list('123'), 'A: B C; B: C; C: ') assignment = None - assert unordered_domain_values('A', assignment, map_coloring_test) == ['1', '2', '3'] + assert unordered_domain_values('A', assignment, map_coloring_test) == ['1', '2', '3'] def test_lcv(): neighbors = parse_neighbors('A: B; B: C; C: ') domains = {'A': [0, 1, 2, 3, 4], 'B': [0, 1, 2, 3, 4, 5], 'C': [0, 1, 2, 3, 4]} - constraints = lambda X, x, Y, y: x % 2 == 0 and (x+y) == 4 + constraints = lambda X, x, Y, y: x % 2 == 0 and (x + y) == 4 csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) assignment = {'A': 0} @@ -347,7 +346,7 @@ def test_min_conflicts(): assert min_conflicts(france) tests = [(usa, None)] * 3 - assert failure_test(min_conflicts, tests) >= 1/3 + assert failure_test(min_conflicts, tests) >= 1 / 3 australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') assert min_conflicts(australia_impossible, 1000) is None @@ -419,9 +418,9 @@ def test_parse_neighbours(): def test_topological_sort(): root = 'NT' - Sort, Parents = topological_sort(australia,root) + Sort, Parents = topological_sort(australia, root) - assert Sort == ['NT','SA','Q','NSW','V','WA'] + assert Sort == ['NT', 'SA', 'Q', 'NSW', 'V', 'WA'] assert Parents['NT'] == None assert Parents['SA'] == 'NT' assert Parents['Q'] == 'SA' @@ -432,10 +431,11 @@ def test_topological_sort(): def test_tree_csp_solver(): australia_small = MapColoringCSP(list('RB'), - 'NT: WA Q; NSW: Q V') + 'NT: WA Q; NSW: Q V') tcs = tree_csp_solver(australia_small) assert (tcs['NT'] == 'R' and tcs['WA'] == 'B' and tcs['Q'] == 'B' and tcs['NSW'] == 'R' and tcs['V'] == 'B') or \ (tcs['NT'] == 'B' and tcs['WA'] == 'R' and tcs['Q'] == 'R' and tcs['NSW'] == 'B' and tcs['V'] == 'R') + if __name__ == "__main__": pytest.main() From 03551fbf2aa3980b915d4b6fefcbc70f24547b03 Mon Sep 17 00:00:00 2001 From: Donato Date: Thu, 11 Apr 2019 03:20:07 +0200 Subject: [PATCH 03/21] added the mentioned AC4 algorithm for constraint propagation AC3 algorithm has non-optimal worst case time-complexity O(cd^3 ), while AC4 algorithm runs in O(cd^2) worst case time --- csp.py | 79 +++++++++++++++++++++++++++++++++++++++++------ tests/test_csp.py | 28 ++++++++++++++++- 2 files changed, 96 insertions(+), 11 deletions(-) diff --git a/csp.py b/csp.py index ee59d4a6b..7a58ca19d 100644 --- a/csp.py +++ b/csp.py @@ -3,7 +3,7 @@ from utils import argmin_random_tie, count, first import search -from collections import defaultdict +from collections import defaultdict, Counter from functools import reduce import itertools @@ -50,13 +50,12 @@ class CSP(search.Problem): def __init__(self, variables, domains, neighbors, constraints): """Construct a CSP problem. If variables is empty, it becomes domains.keys().""" + super().__init__(()) variables = variables or list(domains.keys()) - self.variables = variables self.domains = domains self.neighbors = neighbors self.constraints = constraints - self.initial = () self.curr_domains = None self.nassigns = 0 @@ -74,10 +73,12 @@ def unassign(self, var, assignment): def nconflicts(self, var, val, assignment): """Return the number of conflicts var=val has with other variables.""" + # Subclasses may implement this more efficiently def conflict(var2): return (var2 in assignment and not self.constraints(var, val, var2, assignment[var2])) + return count(conflict(v) for v in self.neighbors[var]) def display(self, assignment): @@ -153,6 +154,7 @@ def conflicted_vars(self, current): return [var for var in self.variables if self.nconflicts(var, current[var], current) > 0] + # ______________________________________________________________________________ # Constraint Propagation with AC-3 @@ -183,6 +185,51 @@ def revise(csp, Xi, Xj, removals): revised = True return revised + +# Constraint Propagation with AC-4 + +def AC4(csp, queue=None, removals=None): + """AC4 algorithm runs in O(cd^2) worst-case time but can be slower + than AC3 on average cases""" + if queue is None: + queue = {(Xi, Xk) for Xi in csp.variables for Xk in csp.neighbors[Xi]} + csp.support_pruning() + support_counter = Counter() + variable_value_pairs_supported = defaultdict(set) + unsupported_variable_value_pairs = [] + # construction and initialization of support sets + while queue: + (Xi, Xj) = queue.pop() + revised = False + for x in csp.curr_domains[Xi][:]: + for y in csp.curr_domains[Xj]: + if csp.constraints(Xi, x, Xj, y): + support_counter[(Xi, x, Xj)] += 1 + variable_value_pairs_supported[(Xj, y)].add((Xi, x)) + if support_counter[(Xi, x, Xj)] == 0: + csp.prune(Xi, x, removals) + revised = True + unsupported_variable_value_pairs.append((Xi, x)) + if revised: + if not csp.curr_domains[Xi]: + return False + # propagation of removed values + while unsupported_variable_value_pairs: + Xj, y = unsupported_variable_value_pairs.pop() + for Xi, x in variable_value_pairs_supported[(Xj, y)]: + revised = False + if x in csp.curr_domains[Xi][:]: + support_counter[(Xi, x, Xj)] -= 1 + if support_counter[(Xi, x, Xj)] == 0: + csp.prune(Xi, x, removals) + revised = True + unsupported_variable_value_pairs.append((Xi, x)) + if revised: + if not csp.curr_domains[Xi]: + return False + return True + + # ______________________________________________________________________________ # CSP Backtracking Search @@ -208,6 +255,7 @@ def num_legal_values(csp, var, assignment): return count(csp.nconflicts(var, val, assignment) == 0 for val in csp.domains[var]) + # Value ordering @@ -221,6 +269,7 @@ def lcv(var, assignment, csp): return sorted(csp.choices(var), key=lambda val: csp.nconflicts(var, val, assignment)) + # Inference @@ -245,6 +294,7 @@ def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) + # The search, proper @@ -274,6 +324,7 @@ def backtrack(assignment): assert result is None or csp.goal_test(result) return result + # ______________________________________________________________________________ # Min-conflicts hillclimbing search for CSPs @@ -302,6 +353,7 @@ def min_conflicts_value(csp, var, current): return argmin_random_tie(csp.domains[var], key=lambda val: csp.nconflicts(var, val, current)) + # ______________________________________________________________________________ @@ -356,7 +408,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents): visited[node] = True for n in neighbors[node]: - if(not visited[n]): + if (not visited[n]): build_topological(n, node, neighbors, visited, stack, parents) parents[node] = parent @@ -366,9 +418,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents): def make_arc_consistent(Xj, Xk, csp): """Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints, by removing the possible values of Xj that cause inconsistencies.""" - #csp.curr_domains[Xj] = [] + # csp.curr_domains[Xj] = [] for val1 in csp.domains[Xj]: - keep = False # Keep or remove val1 + keep = False # Keep or remove val1 for val2 in csp.domains[Xk]: if csp.constraints(Xj, val1, Xk, val2): # Found a consistent assignment for val1, keep it @@ -393,6 +445,7 @@ def assign_value(Xj, Xk, csp, assignment): # No consistent assignment available return None + # ______________________________________________________________________________ # Map-Coloring Problems @@ -468,6 +521,7 @@ def parse_neighbors(neighbors, variables=None): PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: AU BO FC PA LR""") + # ______________________________________________________________________________ # n-Queens Problem @@ -503,16 +557,16 @@ def __init__(self, n): CSP.__init__(self, list(range(n)), UniversalDict(list(range(n))), UniversalDict(list(range(n))), queen_constraint) - self.rows = [0]*n - self.ups = [0]*(2*n - 1) - self.downs = [0]*(2*n - 1) + self.rows = [0] * n + self.ups = [0] * (2 * n - 1) + self.downs = [0] * (2 * n - 1) def nconflicts(self, var, val, assignment): """The number of conflicts, as recorded with each assignment. Count conflicts in row and in up, down diagonals. If there is a queen there, it can't conflict with itself, so subtract 3.""" n = len(self.variables) - c = self.rows[val] + self.downs[var+val] + self.ups[var-val+n-1] + c = self.rows[val] + self.downs[var + val] + self.ups[var - val + n - 1] if assignment.get(var, None) == val: c -= 3 return c @@ -560,6 +614,7 @@ def display(self, assignment): print(str(self.nconflicts(var, val, assignment)) + ch, end=' ') print() + # ______________________________________________________________________________ # Sudoku @@ -646,9 +701,12 @@ def show_cell(cell): return str(assignment.get(cell, '.')) def abut(lines1, lines2): return list( map(' | '.join, list(zip(lines1, lines2)))) + print('\n------+-------+------\n'.join( '\n'.join(reduce( abut, map(show_box, brow))) for brow in self.bgrid)) + + # ______________________________________________________________________________ # The Zebra Puzzle @@ -716,6 +774,7 @@ def zebra_constraint(A, a, B, b, recurse=0): (A in Smokes and B in Smokes)): return not same raise Exception('error') + return CSP(variables, domains, neighbors, zebra_constraint) diff --git a/tests/test_csp.py b/tests/test_csp.py index 77b35c796..02852b4f2 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -211,13 +211,39 @@ def test_AC3(): removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) domains = {'A': [2, 4], 'B': [3, 5]} - constraints = lambda X, x, Y, y: int(x) > int(y) + constraints = lambda X, x, Y, y: x > y removals = [] csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) assert AC3(csp, removals=removals) +def test_AC4(): + neighbors = parse_neighbors('A: B; B: ') + domains = {'A': [0, 1, 2, 3, 4], 'B': [0, 1, 2, 3, 4]} + constraints = lambda X, x, Y, y: x % 2 == 0 and (x + y) == 4 and y % 2 != 0 + removals = [] + + csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) + + assert AC4(csp, removals=removals) is False + + constraints = lambda X, x, Y, y: (x % 2) == 0 and (x + y) == 4 + removals = [] + csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) + + assert AC4(csp, removals=removals) is True + assert (removals == [('A', 1), ('A', 3), ('B', 1), ('B', 3)] or + removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) + + domains = {'A': [2, 4], 'B': [3, 5]} + constraints = lambda X, x, Y, y: (X == 'A' and Y == 'B') or (X == 'B' and Y == 'A') and x < y + removals = [] + csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) + + assert AC4(csp, removals=removals) + + def test_first_unassigned_variable(): map_coloring_test = MapColoringCSP(list('123'), 'A: B C; B: C; C: ') assignment = {'A': '1', 'B': '2'} From 6986247481a05f1e558b93b2bf3cdae395f9c4ee Mon Sep 17 00:00:00 2001 From: Donato Date: Thu, 11 Apr 2019 22:01:42 +0200 Subject: [PATCH 04/21] added doctest in Sudoku for AC4 and and the possibility of choosing the constant propagation algorithm in mac inference --- csp.py | 44 +++++++++++++++++++++++++++++++++++++------- tests/test_csp.py | 8 ++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/csp.py b/csp.py index 7a58ca19d..f2235091d 100644 --- a/csp.py +++ b/csp.py @@ -290,9 +290,9 @@ def forward_checking(csp, var, value, assignment, removals): return True -def mac(csp, var, value, assignment, removals): +def mac(csp, var, value, assignment, removals, constraint_propagation=AC3): """Maintain arc consistency.""" - return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) + return constraint_propagation(csp, {(X, var) for X in csp.neighbors[var]}, removals) # The search, proper @@ -326,11 +326,11 @@ def backtrack(assignment): # ______________________________________________________________________________ -# Min-conflicts hillclimbing search for CSPs +# Min-conflicts Hill Climbing search for CSPs def min_conflicts(csp, max_steps=100000): - """Solve a CSP by stochastic hillclimbing on the number of conflicts.""" + """Solve a CSP by stochastic Hill Climbing on the number of conflicts.""" # Generate a complete assignment for all variables (probably with conflicts) csp.current = current = {} for var in csp.variables: @@ -532,7 +532,7 @@ def queen_constraint(A, a, B, b): return A == B or (a != b and A + a != B + b and A - a != B - b) -class NQueensCSP(CSP): +class NQueens(CSP): """Make a CSP for the nQueens problem for search with min_conflicts. Suitable for large n, it uses only data structures of size O(n). Think of placing queens one per column, from left to right. @@ -548,7 +548,7 @@ class NQueensCSP(CSP): a variable, and a best value for the variable, are each O(n). If you want, you can keep track of conflicted variables, then variable selection will also be O(1). - >>> len(backtracking_search(NQueensCSP(8))) + >>> len(backtracking_search(NQueens(8))) 8 """ @@ -673,7 +673,37 @@ class Sudoku(CSP): >>> h = Sudoku(harder1) >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None True - """ # noqa + + >>> e = Sudoku(easy1) + >>> e.display(e.infer_assignment()) + . . 3 | . 2 . | 6 . . + 9 . . | 3 . 5 | . . 1 + . . 1 | 8 . 6 | 4 . . + ------+-------+------ + . . 8 | 1 . 2 | 9 . . + 7 . . | . . . | . . 8 + . . 6 | 7 . 8 | 2 . . + ------+-------+------ + . . 2 | 6 . 9 | 5 . . + 8 . . | 2 . 3 | . . 9 + . . 5 | . 1 . | 3 . . + >>> AC4(e); e.display(e.infer_assignment()) + True + 4 8 3 | 9 2 1 | 6 5 7 + 9 6 7 | 3 4 5 | 8 2 1 + 2 5 1 | 8 7 6 | 4 9 3 + ------+-------+------ + 5 4 8 | 1 3 2 | 9 7 6 + 7 2 9 | 5 6 4 | 1 3 8 + 1 3 6 | 7 9 8 | 2 4 5 + ------+-------+------ + 3 7 2 | 6 8 9 | 5 1 4 + 8 1 4 | 2 5 3 | 7 6 9 + 6 9 5 | 4 1 7 | 3 8 2 + >>> h = Sudoku(harder1) + >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None + True + """ R3 = _R3 Cell = _CELL diff --git a/tests/test_csp.py b/tests/test_csp.py index 02852b4f2..269d0848f 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -376,12 +376,12 @@ def test_min_conflicts(): australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') assert min_conflicts(australia_impossible, 1000) is None - assert min_conflicts(NQueensCSP(2), 1000) is None - assert min_conflicts(NQueensCSP(3), 1000) is None + assert min_conflicts(NQueens(2), 1000) is None + assert min_conflicts(NQueens(3), 1000) is None def test_nqueens_csp(): - csp = NQueensCSP(8) + csp = NQueens(8) assignment = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} csp.assign(5, 5, assignment) @@ -428,7 +428,7 @@ def test_nqueens_csp(): assert 6 not in assignment for n in range(5, 9): - csp = NQueensCSP(n) + csp = NQueens(n) solution = min_conflicts(csp) assert not solution or sorted(solution.values()) == list(range(n)) From b3cd24c511a82275f5b43c9f176396e6ba05f67e Mon Sep 17 00:00:00 2001 From: Donato Date: Thu, 11 Apr 2019 22:21:53 +0200 Subject: [PATCH 05/21] removed useless doctest for AC4 in Sudoku because AC4's tests are already present in test_csp.py --- csp.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/csp.py b/csp.py index f2235091d..4630c49d7 100644 --- a/csp.py +++ b/csp.py @@ -673,36 +673,6 @@ class Sudoku(CSP): >>> h = Sudoku(harder1) >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None True - - >>> e = Sudoku(easy1) - >>> e.display(e.infer_assignment()) - . . 3 | . 2 . | 6 . . - 9 . . | 3 . 5 | . . 1 - . . 1 | 8 . 6 | 4 . . - ------+-------+------ - . . 8 | 1 . 2 | 9 . . - 7 . . | . . . | . . 8 - . . 6 | 7 . 8 | 2 . . - ------+-------+------ - . . 2 | 6 . 9 | 5 . . - 8 . . | 2 . 3 | . . 9 - . . 5 | . 1 . | 3 . . - >>> AC4(e); e.display(e.infer_assignment()) - True - 4 8 3 | 9 2 1 | 6 5 7 - 9 6 7 | 3 4 5 | 8 2 1 - 2 5 1 | 8 7 6 | 4 9 3 - ------+-------+------ - 5 4 8 | 1 3 2 | 9 7 6 - 7 2 9 | 5 6 4 | 1 3 8 - 1 3 6 | 7 9 8 | 2 4 5 - ------+-------+------ - 3 7 2 | 6 8 9 | 5 1 4 - 8 1 4 | 2 5 3 | 7 6 9 - 6 9 5 | 4 1 7 | 3 8 2 - >>> h = Sudoku(harder1) - >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None - True """ R3 = _R3 From 9e0fa550e85081cf5b92fb6a3418384ab5a9fdfd Mon Sep 17 00:00:00 2001 From: Donato Date: Tue, 18 Jun 2019 00:27:22 +0200 Subject: [PATCH 06/21] added map coloring SAT problems --- csp.py | 43 ++++---- logic.py | 241 ++++++++++++++++++++++++++++++-------------- tests/test_csp.py | 28 ++--- tests/test_logic.py | 49 +++++---- 4 files changed, 230 insertions(+), 131 deletions(-) diff --git a/csp.py b/csp.py index 4630c49d7..c336d7288 100644 --- a/csp.py +++ b/csp.py @@ -447,7 +447,7 @@ def assign_value(Xj, Xk, csp, assignment): # ______________________________________________________________________________ -# Map-Coloring Problems +# Map Coloring Problems class UniversalDict: @@ -499,27 +499,26 @@ def parse_neighbors(neighbors, variables=None): return dic -australia = MapColoringCSP(list('RGB'), - 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') - -usa = MapColoringCSP(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - -france = MapColoringCSP(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") +australia_csp = MapColoringCSP(list('RGB'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') + +usa_csp = MapColoringCSP(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + +france_csp = MapColoringCSP(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") # ______________________________________________________________________________ diff --git a/logic.py b/logic.py index 6aacc4f95..066c6e7e2 100644 --- a/logic.py +++ b/logic.py @@ -30,7 +30,7 @@ unify Do unification of two FOL sentences diff, simp Symbolic differentiation and simplification """ - +from csp import parse_neighbors, UniversalDict from utils import ( removeall, unique, first, argmax, probability, isnumber, issequence, Expr, expr, subexpressions @@ -42,11 +42,11 @@ import random from collections import defaultdict + # ______________________________________________________________________________ class KB: - """A knowledge base to which you can tell and ask sentences. To create a KB, first subclass this class and implement tell, ask_generator, and retract. Why ask_generator instead of ask? @@ -106,6 +106,7 @@ def retract(self, sentence): if c in self.clauses: self.clauses.remove(c) + # ______________________________________________________________________________ @@ -319,6 +320,7 @@ def pl_true(exp, model={}): else: raise ValueError("illegal operator in logic expression" + str(exp)) + # ______________________________________________________________________________ # Convert to Conjunctive Normal Form (CNF) @@ -368,6 +370,7 @@ def move_not_inwards(s): if s.op == '~': def NOT(b): return move_not_inwards(~b) + a = s.args[0] if a.op == '~': return move_not_inwards(a.args[0]) # ~~A ==> A @@ -445,6 +448,7 @@ def collect(subargs): collect(arg.args) else: result.append(arg) + collect(args) return result @@ -468,6 +472,7 @@ def disjuncts(s): """ return dissociate('|', [s]) + # ______________________________________________________________________________ @@ -481,7 +486,7 @@ def pl_resolution(KB, alpha): while True: n = len(clauses) pairs = [(clauses[i], clauses[j]) - for i in range(n) for j in range(i+1, n)] + for i in range(n) for j in range(i + 1, n)] for (ci, cj) in pairs: resolvents = pl_resolve(ci, cj) if False in resolvents: @@ -505,6 +510,7 @@ def pl_resolve(ci, cj): clauses.append(associate('|', dnew)) return clauses + # ______________________________________________________________________________ @@ -560,7 +566,6 @@ def pl_fc_entails(KB, q): """ wumpus_world_inference = expr("(B11 <=> (P12 | P21)) & ~B11") - """ [Figure 7.16] Propositional Logic Forward Chaining example """ @@ -572,9 +577,11 @@ def pl_fc_entails(KB, q): Definite clauses KB example """ definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', + 'C']: definite_clauses_KB.tell(expr(clause)) + # ______________________________________________________________________________ # DPLL-Satisfiable [Figure 7.17] @@ -665,7 +672,7 @@ def unit_clause_assign(clause, model): if model[sym] == positive: return None, None # clause already True elif P: - return None, None # more than 1 unbound variable + return None, None # more than 1 unbound variable else: P, value = sym, positive return P, value @@ -684,6 +691,7 @@ def inspect_literal(literal): else: return literal, True + # ______________________________________________________________________________ # Walk-SAT [Figure 7.18] @@ -714,95 +722,186 @@ def sat_count(sym): count = len([clause for clause in clauses if pl_true(clause, model)]) model[sym] = not model[sym] return count + sym = argmax(prop_symbols(clause), key=sat_count) model[sym] = not model[sym] # If no solution is found within the flip limit, we return failure return None + +# ______________________________________________________________________________ +# Map Coloring Problems + + +def MapColoringSAT(colors, neighbors): + """Make a SAT for the problem of coloring a map with different colors + for any two adjacent regions. Arguments are a list of colors, and a + dict of {region: [neighbor,...]} entries. This dict may also be + specified as a string of the form defined by parse_neighbors.""" + if isinstance(neighbors, str): + neighbors = parse_neighbors(neighbors) + colors = UniversalDict(colors) + part = str() + t = str() + for x in neighbors.keys(): + part += '(' + l = 0 + for c in colors[x]: + l += 1 + part += str(x) + '_' + str(c) + t += str(x) + '_' + str(c) + if l != len(colors[x]): + part += ' | ' + t += '|' + part += ') & ' + list = t.split('|') + t = str() + for idx, val in enumerate(list): + for x in list[idx + 1:]: + part += '~(' + val + ' & ' + x + ') & ' + not_part = str() + visit = set() + for x in neighbors.keys(): + adj = set(neighbors[x]) + adj = adj - visit + visit.add(x) + for n in adj: + for col in colors[n]: + not_part += '~(' + str(x) + '_' + str(col) + ' & ' + not_part += str(n) + '_' + str(col) + ') & ' + clause = part + not_part[:len(not_part) - 2] + return expr(clause) + + +australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) + +france_sat = MapColoringSAT(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") + +usa_sat = MapColoringSAT(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + + # ______________________________________________________________________________ # Expr functions for WumpusKB and HybridWumpusAgent -def facing_east (time): +def facing_east(time): return Expr('FacingEast', time) -def facing_west (time): + +def facing_west(time): return Expr('FacingWest', time) -def facing_north (time): + +def facing_north(time): return Expr('FacingNorth', time) -def facing_south (time): + +def facing_south(time): return Expr('FacingSouth', time) -def wumpus (x, y): + +def wumpus(x, y): return Expr('W', x, y) + def pit(x, y): return Expr('P', x, y) + def breeze(x, y): return Expr('B', x, y) + def stench(x, y): return Expr('S', x, y) + def wumpus_alive(time): return Expr('WumpusAlive', time) + def have_arrow(time): return Expr('HaveArrow', time) + def percept_stench(time): return Expr('Stench', time) + def percept_breeze(time): return Expr('Breeze', time) + def percept_glitter(time): return Expr('Glitter', time) + def percept_bump(time): return Expr('Bump', time) + def percept_scream(time): return Expr('Scream', time) + def move_forward(time): return Expr('Forward', time) + def shoot(time): return Expr('Shoot', time) + def turn_left(time): return Expr('TurnLeft', time) + def turn_right(time): return Expr('TurnRight', time) + def ok_to_move(x, y, time): return Expr('OK', x, y, time) -def location(x, y, time = None): + +def location(x, y, time=None): if time is None: return Expr('L', x, y) else: return Expr('L', x, y, time) + # Symbols def implies(lhs, rhs): return Expr('==>', lhs, rhs) + def equiv(lhs, rhs): return Expr('<=>', lhs, rhs) + # Helper Function def new_disjunction(sentences): t = sentences[0] - for i in range(1,len(sentences)): + for i in range(1, len(sentences)): t |= sentences[i] return t @@ -815,59 +914,56 @@ class WumpusKB(PropKB): Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. """ - def __init__(self,dimrow): + def __init__(self, dimrow): super().__init__() self.dimrow = dimrow - self.tell( ~wumpus(1, 1) ) - self.tell( ~pit(1, 1) ) + self.tell(~wumpus(1, 1)) + self.tell(~pit(1, 1)) - for y in range(1, dimrow+1): - for x in range(1, dimrow+1): + for y in range(1, dimrow + 1): + for x in range(1, dimrow + 1): pits_in = list() wumpus_in = list() - if x > 1: # West room exists + if x > 1: # West room exists pits_in.append(pit(x - 1, y)) wumpus_in.append(wumpus(x - 1, y)) - if y < dimrow: # North room exists + if y < dimrow: # North room exists pits_in.append(pit(x, y + 1)) wumpus_in.append(wumpus(x, y + 1)) - if x < dimrow: # East room exists + if x < dimrow: # East room exists pits_in.append(pit(x + 1, y)) wumpus_in.append(wumpus(x + 1, y)) - if y > 1: # South room exists + if y > 1: # South room exists pits_in.append(pit(x, y - 1)) wumpus_in.append(wumpus(x, y - 1)) self.tell(equiv(breeze(x, y), new_disjunction(pits_in))) self.tell(equiv(stench(x, y), new_disjunction(wumpus_in))) - - ## Rule that describes existence of at least one Wumpus + # Rule that describes existence of at least one Wumpus wumpus_at_least = list() - for x in range(1, dimrow+1): + for x in range(1, dimrow + 1): for y in range(1, dimrow + 1): wumpus_at_least.append(wumpus(x, y)) self.tell(new_disjunction(wumpus_at_least)) - - ## Rule that describes existence of at most one Wumpus - for i in range(1, dimrow+1): - for j in range(1, dimrow+1): - for u in range(1, dimrow+1): - for v in range(1, dimrow+1): - if i!=u or j!=v: + # Rule that describes existence of at most one Wumpus + for i in range(1, dimrow + 1): + for j in range(1, dimrow + 1): + for u in range(1, dimrow + 1): + for v in range(1, dimrow + 1): + if i != u or j != v: self.tell(~wumpus(i, j) | ~wumpus(u, v)) - - ## Temporal rules at time zero + # Temporal rules at time zero self.tell(location(1, 1, 0)) - for i in range(1, dimrow+1): + for i in range(1, dimrow + 1): for j in range(1, dimrow + 1): self.tell(implies(location(i, j, 0), equiv(percept_breeze(0), breeze(i, j)))) self.tell(implies(location(i, j, 0), equiv(percept_stench(0), stench(i, j)))) @@ -881,7 +977,6 @@ def __init__(self,dimrow): self.tell(~facing_south(0)) self.tell(~facing_west(0)) - def make_action_sentence(self, action, time): actions = [move_forward(time), shoot(time), turn_left(time), turn_right(time)] @@ -895,7 +990,7 @@ def make_percept_sentence(self, percept, time): # Glitter, Bump, Stench, Breeze, Scream flags = [0, 0, 0, 0, 0] - ## Things perceived + # Things perceived if isinstance(percept, Glitter): flags[0] = 1 self.tell(percept_glitter(time)) @@ -912,7 +1007,7 @@ def make_percept_sentence(self, percept, time): flags[4] = 1 self.tell(percept_scream(time)) - ## Things not perceived + # Things not perceived for i in range(len(flags)): if flags[i] == 0: if i == 0: @@ -926,15 +1021,14 @@ def make_percept_sentence(self, percept, time): elif i == 4: self.tell(~percept_scream(time)) - def add_temporal_sentences(self, time): if time == 0: return t = time - 1 - ## current location rules - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + # current location rules + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): self.tell(implies(location(i, j, time), equiv(percept_breeze(time), breeze(i, j)))) self.tell(implies(location(i, j, time), equiv(percept_stench(time), stench(i, j)))) @@ -956,15 +1050,15 @@ def add_temporal_sentences(self, time): if j != self.dimrow: s.append(location(i, j + 1, t) & facing_south(t) & move_forward(t)) - ## add sentence about location i,j + # add sentence about location i,j self.tell(new_disjunction(s)) - ## add sentence about safety of location i,j + # add sentence about safety of location i,j self.tell( equiv(ok_to_move(i, j, time), ~pit(i, j) & ~wumpus(i, j) & wumpus_alive(time)) ) - ## Rules about current orientation + # Rules about current orientation a = facing_north(t) & turn_right(t) b = facing_south(t) & turn_left(t) @@ -990,16 +1084,15 @@ def add_temporal_sentences(self, time): s = equiv(facing_south(time), a | b | c) self.tell(s) - ## Rules about last action + # Rules about last action self.tell(equiv(move_forward(t), ~turn_right(t) & ~turn_left(t))) - ##Rule about the arrow + # Rule about the arrow self.tell(equiv(have_arrow(time), have_arrow(t) & ~shoot(t))) - ##Rule about Wumpus (dead or alive) + # Rule about Wumpus (dead or alive) self.tell(equiv(wumpus_alive(time), wumpus_alive(t) & ~percept_scream(time))) - def ask_if_true(self, query): return pl_resolution(self, query) @@ -1007,13 +1100,12 @@ def ask_if_true(self, query): # ______________________________________________________________________________ -class WumpusPosition(): +class WumpusPosition: def __init__(self, x, y, orientation): self.X = x self.Y = y self.orientation = orientation - def get_location(self): return self.X, self.Y @@ -1029,18 +1121,19 @@ def set_orientation(self, orientation): def __eq__(self, other): if other.get_location() == self.get_location() and \ - other.get_orientation()==self.get_orientation(): + other.get_orientation() == self.get_orientation(): return True else: return False + # ______________________________________________________________________________ class HybridWumpusAgent(Agent): """An agent for the wumpus world that does logical inference. [Figure 7.20]""" - def __init__(self,dimentions): + def __init__(self, dimentions): self.dimrow = dimentions self.kb = WumpusKB(self.dimrow) self.t = 0 @@ -1048,15 +1141,14 @@ def __init__(self,dimentions): self.current_position = WumpusPosition(1, 1, 'UP') super().__init__(self.execute) - def execute(self, percept): self.kb.make_percept_sentence(percept, self.t) self.kb.add_temporal_sentences(self.t) temp = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if self.kb.ask_if_true(location(i, j, self.t)): temp.append(i) temp.append(j) @@ -1071,8 +1163,8 @@ def execute(self, percept): self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT') safe_points = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if self.kb.ask_if_true(ok_to_move(i, j, self.t)): safe_points.append([i, j]) @@ -1080,14 +1172,14 @@ def execute(self, percept): goals = list() goals.append([1, 1]) self.plan.append('Grab') - actions = self.plan_route(self.current_position,goals,safe_points) + actions = self.plan_route(self.current_position, goals, safe_points) self.plan.extend(actions) self.plan.append('Climb') if len(self.plan) == 0: unvisited = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): for k in range(self.t): if self.kb.ask_if_true(location(i, j, k)): unvisited.append([i, j]) @@ -1097,13 +1189,13 @@ def execute(self, percept): if u not in unvisited_and_safe and s == u: unvisited_and_safe.append(u) - temp = self.plan_route(self.current_position,unvisited_and_safe,safe_points) + temp = self.plan_route(self.current_position, unvisited_and_safe, safe_points) self.plan.extend(temp) if len(self.plan) == 0 and self.kb.ask_if_true(have_arrow(self.t)): possible_wumpus = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if not self.kb.ask_if_true(wumpus(i, j)): possible_wumpus.append([i, j]) @@ -1112,8 +1204,8 @@ def execute(self, percept): if len(self.plan) == 0: not_unsafe = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if not self.kb.ask_if_true(ok_to_move(i, j, self.t)): not_unsafe.append([i, j]) temp = self.plan_route(self.current_position, not_unsafe, safe_points) @@ -1133,19 +1225,17 @@ def execute(self, percept): return action - def plan_route(self, current, goals, allowed): problem = PlanRoute(current, goals, allowed, self.dimrow) return astar_search(problem).solution() - def plan_shot(self, current, goals, allowed): shooting_positions = set() for loc in goals: x = loc[0] y = loc[1] - for i in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): if i < x: shooting_positions.add(WumpusPosition(i, y, 'EAST')) if i > x: @@ -1157,7 +1247,7 @@ def plan_shot(self, current, goals, allowed): # Can't have a shooting position from any of the rooms the Wumpus could reside orientations = ['EAST', 'WEST', 'NORTH', 'SOUTH'] - for loc in goals: + for loc in goals: for orientation in orientations: shooting_positions.remove(WumpusPosition(loc[0], loc[1], orientation)) @@ -1186,7 +1276,7 @@ def translate_to_SAT(init, transition, goal, time): # Symbol claiming state s at time t state_counter = itertools.count() for s in states: - for t in range(time+1): + for t in range(time + 1): state_sym[s, t] = Expr("State_{}".format(next(state_counter))) # Add initial state axiom @@ -1206,11 +1296,11 @@ def translate_to_SAT(init, transition, goal, time): "Transition_{}".format(next(transition_counter))) # Change the state from s to s_ - clauses.append(action_sym[s, action, t] |'==>'| state_sym[s, t]) - clauses.append(action_sym[s, action, t] |'==>'| state_sym[s_, t + 1]) + clauses.append(action_sym[s, action, t] | '==>' | state_sym[s, t]) + clauses.append(action_sym[s, action, t] | '==>' | state_sym[s_, t + 1]) # Allow only one state at any time - for t in range(time+1): + for t in range(time + 1): # must be a state at any time clauses.append(associate('|', [state_sym[s, t] for s in states])) @@ -1363,6 +1453,7 @@ def standardize_variables(sentence, dic=None): standardize_variables.counter = itertools.count() + # ______________________________________________________________________________ @@ -1404,6 +1495,7 @@ def fol_fc_ask(KB, alpha): """A simple forward-chaining algorithm. [Figure 9.3]""" # TODO: Improve efficiency kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)}) + def enum_subst(p): query_vars = list({v for clause in p for v in variables(clause)}) for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)): @@ -1497,6 +1589,7 @@ def fol_bc_and(KB, goals, theta): 'Enemy(Nono, America)' ])) + # ______________________________________________________________________________ # Example application (not in the book). diff --git a/tests/test_csp.py b/tests/test_csp.py index 269d0848f..ca4075be8 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -10,16 +10,16 @@ def test_csp_assign(): var = 10 val = 5 assignment = {} - australia.assign(var, val, assignment) + australia_csp.assign(var, val, assignment) - assert australia.nassigns == 1 + assert australia_csp.nassigns == 1 assert assignment[var] == val def test_csp_unassign(): var = 10 assignment = {var: 5} - australia.unassign(var, assignment) + australia_csp.unassign(var, assignment) assert var not in assignment @@ -356,22 +356,22 @@ def test_forward_checking(): def test_backtracking_search(): - assert backtracking_search(australia) - assert backtracking_search(australia, select_unassigned_variable=mrv) - assert backtracking_search(australia, order_domain_values=lcv) - assert backtracking_search(australia, select_unassigned_variable=mrv, + assert backtracking_search(australia_csp) + assert backtracking_search(australia_csp, select_unassigned_variable=mrv) + assert backtracking_search(australia_csp, order_domain_values=lcv) + assert backtracking_search(australia_csp, select_unassigned_variable=mrv, order_domain_values=lcv) - assert backtracking_search(australia, inference=forward_checking) - assert backtracking_search(australia, inference=mac) - assert backtracking_search(usa, select_unassigned_variable=mrv, + assert backtracking_search(australia_csp, inference=forward_checking) + assert backtracking_search(australia_csp, inference=mac) + assert backtracking_search(usa_csp, select_unassigned_variable=mrv, order_domain_values=lcv, inference=mac) def test_min_conflicts(): - assert min_conflicts(australia) - assert min_conflicts(france) + assert min_conflicts(australia_csp) + assert min_conflicts(france_csp) - tests = [(usa, None)] * 3 + tests = [(usa_csp, None)] * 3 assert failure_test(min_conflicts, tests) >= 1 / 3 australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') @@ -444,7 +444,7 @@ def test_parse_neighbours(): def test_topological_sort(): root = 'NT' - Sort, Parents = topological_sort(australia, root) + Sort, Parents = topological_sort(australia_csp, root) assert Sort == ['NT', 'SA', 'Q', 'NSW', 'V', 'WA'] assert Parents['NT'] == None diff --git a/tests/test_logic.py b/tests/test_logic.py index 378f1f0fc..a2ac8c080 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -3,8 +3,9 @@ from utils import expr_handle_infix_ops, count, Symbol definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: - definite_clauses_KB.tell(expr(clause)) +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', + 'C']: + definite_clauses_KB.tell(expr(clause)) def test_is_symbol(): @@ -47,7 +48,7 @@ def test_extend(): def test_subst(): - assert subst({x: 42, y:0}, F(x) + y) == (F(42) + 0) + assert subst({x: 42, y: 0}, F(x) + y) == (F(42) + 0) def test_PropKB(): @@ -55,7 +56,7 @@ def test_PropKB(): assert count(kb.ask(expr) for expr in [A, C, D, E, Q]) is 0 kb.tell(A & E) assert kb.ask(A) == kb.ask(E) == {} - kb.tell(E |'==>'| C) + kb.tell(E | '==>' | C) assert kb.ask(C) == {} kb.retract(E) assert kb.ask(E) is False @@ -94,7 +95,8 @@ def test_is_definite_clause(): def test_parse_definite_clause(): assert parse_definite_clause(expr('A & B & C & D ==> E')) == ([A, B, C, D], E) assert parse_definite_clause(expr('Farmer(Mac)')) == ([], expr('Farmer(Mac)')) - assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ([expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) + assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ( + [expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) def test_pl_true(): @@ -131,28 +133,28 @@ def test_dpll(): assert (dpll_satisfiable(A & ~B & C & (A | ~D) & (~E | ~D) & (C | ~D) & (~A | ~F) & (E | ~F) & (~D | ~F) & (B | ~C | D) & (A | ~E | F) & (~A | E | D)) == {B: False, C: True, A: True, F: False, D: True, E: False}) - assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} - assert dpll_satisfiable((A | (B & C)) |'<=>'| ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} - assert dpll_satisfiable(A |'<=>'| B) == {A: True, B: True} + assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} + assert dpll_satisfiable((A | (B & C)) | '<=>' | ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} + assert dpll_satisfiable(A | '<=>' | B) == {A: True, B: True} assert dpll_satisfiable(A & ~B) == {A: True, B: False} assert dpll_satisfiable(P & ~P) is False def test_find_pure_symbol(): - assert find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A]) == (A, True) - assert find_pure_symbol([A, B, C], [~A|~B,~B|~C,C|A]) == (B, False) - assert find_pure_symbol([A, B, C], [~A|B,~B|~C,C|A]) == (None, None) + assert find_pure_symbol([A, B, C], [A | ~B, ~B | ~C, C | A]) == (A, True) + assert find_pure_symbol([A, B, C], [~A | ~B, ~B | ~C, C | A]) == (B, False) + assert find_pure_symbol([A, B, C], [~A | B, ~B | ~C, C | A]) == (None, None) def test_unit_clause_assign(): - assert unit_clause_assign(A|B|C, {A:True}) == (None, None) - assert unit_clause_assign(B|C, {A:True}) == (None, None) - assert unit_clause_assign(B|~A, {A:True}) == (B, True) + assert unit_clause_assign(A | B | C, {A: True}) == (None, None) + assert unit_clause_assign(B | C, {A: True}) == (None, None) + assert unit_clause_assign(B | ~A, {A: True}) == (B, True) def test_find_unit_clause(): - assert find_unit_clause([A|B|C, B|~C, ~A|~B], {A:True}) == (B, False) - + assert find_unit_clause([A | B | C, B | ~C, ~A | ~B], {A: True}) == (B, False) + def test_unify(): assert unify(x, x, {}) == {} @@ -175,9 +177,9 @@ def test_tt_entails(): assert tt_entails(P & Q, Q) assert not tt_entails(P | Q, Q) assert tt_entails(A & (B | C) & E & F & ~(P | Q), A & E & F & ~P & ~Q) - assert not tt_entails(P |'<=>'| Q, Q) - assert tt_entails((P |'==>'| Q) & P, Q) - assert not tt_entails((P |'<=>'| Q) & ~P, Q) + assert not tt_entails(P | '<=>' | Q, Q) + assert tt_entails((P | '==>' | Q) & P, Q) + assert not tt_entails((P | '<=>' | Q) & ~P, Q) def test_prop_symbols(): @@ -231,12 +233,13 @@ def test_move_not_inwards(): def test_distribute_and_over_or(): - def test_entailment(s, has_and = False): + def test_entailment(s, has_and=False): result = distribute_and_over_or(s) if has_and: assert result.op == '&' assert tt_entails(s, result) assert tt_entails(result, s) + test_entailment((A & B) | C, True) test_entailment((A | B) & C, True) test_entailment((A | B) | C, False) @@ -253,7 +256,8 @@ def test_to_cnf(): assert repr(to_cnf("a | (b & c) | d")) == '((b | a | d) & (c | a | d))' assert repr(to_cnf("A & (B | (D & E))")) == '(A & (D | B) & (E | B))' assert repr(to_cnf("A | (B | (C | (D & E)))")) == '((D | A | B | C) & (E | A | B | C))' - assert repr(to_cnf('(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' + assert repr(to_cnf( + '(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' def test_pl_resolution(): @@ -281,6 +285,7 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) + assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]' @@ -295,6 +300,7 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) + assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' assert repr(test_ask('Enemy(x, America)', crime_kb)) == '[{x: Nono}]' assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' @@ -316,6 +322,7 @@ def check_SAT(clauses, single_solution={}): if single_solution: # Cross check the solution if only one exists assert all(pl_true(x, single_solution) for x in clauses) assert soln == single_solution + # Test WalkSat for problems with solution check_SAT([A & B, A & C]) check_SAT([A | B, P & Q, P & B]) From f743146c43b28e0525b0f0b332faebc78c15946f Mon Sep 17 00:00:00 2001 From: Donato Date: Tue, 18 Jun 2019 01:00:29 +0200 Subject: [PATCH 07/21] fixed typo errors and removed unnecessary brackets --- logic.py | 11 ++-- tests/test_agents.py | 122 +++++++++++++++++++++++-------------------- 2 files changed, 69 insertions(+), 64 deletions(-) diff --git a/logic.py b/logic.py index 066c6e7e2..0d6c2dca1 100644 --- a/logic.py +++ b/logic.py @@ -911,7 +911,7 @@ def new_disjunction(sentences): class WumpusKB(PropKB): """ - Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. + Create a Knowledge Base that contains the a temporal "Wumpus physics" and temporal rules with time zero. """ def __init__(self, dimrow): @@ -1120,8 +1120,7 @@ def set_orientation(self, orientation): self.orientation = orientation def __eq__(self, other): - if other.get_location() == self.get_location() and \ - other.get_orientation() == self.get_orientation(): + if other.get_location() == self.get_location() and other.get_orientation() == self.get_orientation(): return True else: return False @@ -1558,8 +1557,8 @@ def fol_bc_and(KB, goals, theta): P11, P12, P21, P22, P31, B11, B21 = expr('P11, P12, P21, P22, P31, B11, B21') wumpus_kb.tell(~P11) -wumpus_kb.tell(B11 | '<=>' | ((P12 | P21))) -wumpus_kb.tell(B21 | '<=>' | ((P11 | P22 | P31))) +wumpus_kb.tell(B11 | '<=>' | (P12 | P21)) +wumpus_kb.tell(B21 | '<=>' | (P11 | P22 | P31)) wumpus_kb.tell(~B11) wumpus_kb.tell(B21) @@ -1620,7 +1619,7 @@ def diff(y, x): elif op == '/': return (v * diff(u, x) - u * diff(v, x)) / (v * v) elif op == '**' and isnumber(x.op): - return (v * u ** (v - 1) * diff(u, x)) + return v * u ** (v - 1) * diff(u, x) elif op == '**': return (v * u ** (v - 1) * diff(u, x) + u ** v * Expr('log')(u) * diff(v, x)) diff --git a/tests/test_agents.py b/tests/test_agents.py index 3c133c32a..a5f0f1b22 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -1,12 +1,12 @@ import random -from agents import Direction + from agents import Agent -from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ - RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ - SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, rule_match +from agents import Direction +from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents, \ + RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ + SimpleReflexAgentProgram, ModelBasedReflexAgentProgram from agents import Wall, Gold, Explorer, Thing, Bump, Glitter, WumpusEnvironment, Pit, \ - VacuumEnvironment, Dirt - + VacuumEnvironment, Dirt random.seed("aima-python") @@ -58,12 +58,12 @@ def test_add(): assert l2.direction == Direction.D -def test_RandomAgentProgram() : - #create a list of all the actions a vacuum cleaner can perform +def test_RandomAgentProgram(): + # create a list of all the actions a vacuum cleaner can perform list = ['Right', 'Left', 'Suck', 'NoOp'] # create a program and then an object of the RandomAgentProgram program = RandomAgentProgram(list) - + agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -72,10 +72,10 @@ def test_RandomAgentProgram() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean' , (0, 0): 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_RandomVacuumAgent() : +def test_RandomVacuumAgent(): # create an object of the RandomVacuumAgent agent = RandomVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -85,7 +85,7 @@ def test_RandomVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} def test_TableDrivenAgent(): @@ -109,22 +109,22 @@ def test_TableDrivenAgent(): # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() # initializing some environment status - environment.status = {loc_A:'Dirty', loc_B:'Dirty'} + environment.status = {loc_A: 'Dirty', loc_B: 'Dirty'} # add agent to the environment environment.add_thing(agent) # run the environment by single step everytime to check how environment evolves using TableDrivenAgentProgram - environment.run(steps = 1) - assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} + environment.run(steps=1) + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} - environment.run(steps = 1) - assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} + environment.run(steps=1) + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} - environment.run(steps = 1) - assert environment.status == {(1,0): 'Clean', (0,0): 'Clean'} + environment.run(steps=1) + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_ReflexVacuumAgent() : +def test_ReflexVacuumAgent(): # create an object of the ReflexVacuumAgent agent = ReflexVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -134,31 +134,31 @@ def test_ReflexVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} def test_SimpleReflexAgentProgram(): class Rule: - + def __init__(self, state, action): self.__state = state self.action = action - + def matches(self, state): return self.__state == state - + loc_A = (0, 0) loc_B = (1, 0) - + # create rules for a two state Vacuum Environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] - + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + def interpret_input(state): return state - + # create a program and then an object of the SimpleReflexAgentProgram - program = SimpleReflexAgentProgram(rules, interpret_input) + program = SimpleReflexAgentProgram(rules, interpret_input) agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -167,7 +167,7 @@ def interpret_input(state): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} def test_ModelBasedReflexAgentProgram(): @@ -185,7 +185,7 @@ def matches(self, state): # create rules for a two-state vacuum environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] def update_state(state, action, percept, model): return percept @@ -203,7 +203,7 @@ def update_state(state, action, percept, model): assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_ModelBasedVacuumAgent() : +def test_ModelBasedVacuumAgent(): # create an object of the ModelBasedVacuumAgent agent = ModelBasedVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -213,10 +213,10 @@ def test_ModelBasedVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_TableDrivenVacuumAgent() : +def test_TableDrivenVacuumAgent(): # create an object of the TableDrivenVacuumAgent agent = TableDrivenVacuumAgent() # create an object of the TrivialVacuumEnvironment @@ -226,10 +226,10 @@ def test_TableDrivenVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0):'Clean', (0, 0):'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_compare_agents() : +def test_compare_agents(): environment = TrivialVacuumEnvironment agents = [ModelBasedVacuumAgent, ReflexVacuumAgent] @@ -263,24 +263,26 @@ def test_TableDrivenAgentProgram(): def test_Agent(): def constant_prog(percept): return percept + agent = Agent(constant_prog) result = agent.program(5) assert result == 5 + def test_VacuumEnvironment(): # Initialize Vacuum Environment - v = VacuumEnvironment(6,6) - #Get an agent + v = VacuumEnvironment(6, 6) + # Get an agent agent = ModelBasedVacuumAgent() agent.direction = Direction(Direction.R) v.add_thing(agent) - v.add_thing(Dirt(), location=(2,1)) + v.add_thing(Dirt(), location=(2, 1)) # Check if things are added properly assert len([x for x in v.things if isinstance(x, Wall)]) == 20 assert len([x for x in v.things if isinstance(x, Dirt)]) == 1 - #Let the action begin! + # Let the action begin! assert v.percept(agent) == ("Clean", "None") v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "None") @@ -288,65 +290,69 @@ def test_VacuumEnvironment(): v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "Bump") v.execute_action(agent, "Suck") - assert v.percept(agent) == ("Clean", "None") + assert v.percept(agent) == ("Clean", "None") old_performance = agent.performance v.execute_action(agent, "NoOp") assert old_performance == agent.performance + def test_WumpusEnvironment(): def constant_prog(percept): return percept + # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) - #Check if things are added properly + # Check if things are added properly assert len([x for x in w.things if isinstance(x, Wall)]) == 20 assert any(map(lambda x: isinstance(x, Gold), w.things)) assert any(map(lambda x: isinstance(x, Explorer), w.things)) - assert not any(map(lambda x: not isinstance(x,Thing), w.things)) + assert not any(map(lambda x: not isinstance(x, Thing), w.things)) - #Check that gold and wumpus are not present on (1,1) - assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x,WumpusEnvironment), - w.list_things_at((1, 1)))) + # Check that gold and wumpus are not present on (1,1) + assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x, WumpusEnvironment), + w.list_things_at((1, 1)))) - #Check if w.get_world() segments objects correctly + # Check if w.get_world() segments objects correctly assert len(w.get_world()) == 6 for row in w.get_world(): assert len(row) == 6 - #Start the game! + # Start the game! agent = [x for x in w.things if isinstance(x, Explorer)][0] gold = [x for x in w.things if isinstance(x, Gold)][0] pit = [x for x in w.things if isinstance(x, Pit)][0] - assert w.is_done()==False + assert w.is_done() == False - #Check Walls + # Check Walls agent.location = (1, 2) percepts = w.percept(agent) assert len(percepts) == 5 - assert any(map(lambda x: isinstance(x,Bump), percepts[0])) + assert any(map(lambda x: isinstance(x, Bump), percepts[0])) - #Check Gold + # Check Gold agent.location = gold.location percepts = w.percept(agent) - assert any(map(lambda x: isinstance(x,Glitter), percepts[4])) - agent.location = (gold.location[0], gold.location[1]+1) + assert any(map(lambda x: isinstance(x, Glitter), percepts[4])) + agent.location = (gold.location[0], gold.location[1] + 1) percepts = w.percept(agent) - assert not any(map(lambda x: isinstance(x,Glitter), percepts[4])) + assert not any(map(lambda x: isinstance(x, Glitter), percepts[4])) - #Check agent death + # Check agent death agent.location = pit.location assert w.in_danger(agent) == True assert agent.alive == False assert agent.killed_by == Pit.__name__ assert agent.performance == -1000 - assert w.is_done()==True + assert w.is_done() == True + def test_WumpusEnvironmentActions(): def constant_prog(percept): return percept + # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) @@ -371,4 +377,4 @@ def constant_prog(percept): w.execute_action(agent, 'Climb') assert not any(map(lambda x: isinstance(x, Explorer), w.things)) - assert w.is_done()==True \ No newline at end of file + assert w.is_done() == True From 20ab0e5afa238a0556e68f173b07ad32d0779d3b Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 16:39:33 +0200 Subject: [PATCH 08/21] reformulated the map coloring problem --- logic.py | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/logic.py b/logic.py index 0d6c2dca1..5ef29212f 100644 --- a/logic.py +++ b/logic.py @@ -741,36 +741,19 @@ def MapColoringSAT(colors, neighbors): if isinstance(neighbors, str): neighbors = parse_neighbors(neighbors) colors = UniversalDict(colors) - part = str() - t = str() - for x in neighbors.keys(): - part += '(' - l = 0 - for c in colors[x]: - l += 1 - part += str(x) + '_' + str(c) - t += str(x) + '_' + str(c) - if l != len(colors[x]): - part += ' | ' - t += '|' - part += ') & ' - list = t.split('|') - t = str() - for idx, val in enumerate(list): - for x in list[idx + 1:]: - part += '~(' + val + ' & ' + x + ') & ' - not_part = str() - visit = set() - for x in neighbors.keys(): - adj = set(neighbors[x]) - adj = adj - visit - visit.add(x) - for n in adj: - for col in colors[n]: - not_part += '~(' + str(x) + '_' + str(col) + ' & ' - not_part += str(n) + '_' + str(col) + ') & ' - clause = part + not_part[:len(not_part) - 2] - return expr(clause) + clauses = [] + for state in neighbors.keys(): + clause = [expr(state + '_' + c) for c in colors[state]] + clauses.append(clause) + for t in itertools.combinations(clause, 2): + clauses.append([~t[0], ~t[1]]) + visited = set() + adj = set(neighbors[state]) - visited + visited.add(state) + for n_state in adj: + for col in colors[n_state]: + clauses.append([expr('~' + state + '_' + col), expr('~' + n_state + '_' + col)]) + return associate('&', map(lambda c: associate('|', c), clauses)) australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) From 404b179fc4cc857c5be57165da81afffabc48d4d Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 16:52:59 +0200 Subject: [PATCH 09/21] Revert "reformulated the map coloring problem" This reverts commit 20ab0e5afa238a0556e68f173b07ad32d0779d3b. --- logic.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/logic.py b/logic.py index 5ef29212f..0d6c2dca1 100644 --- a/logic.py +++ b/logic.py @@ -741,19 +741,36 @@ def MapColoringSAT(colors, neighbors): if isinstance(neighbors, str): neighbors = parse_neighbors(neighbors) colors = UniversalDict(colors) - clauses = [] - for state in neighbors.keys(): - clause = [expr(state + '_' + c) for c in colors[state]] - clauses.append(clause) - for t in itertools.combinations(clause, 2): - clauses.append([~t[0], ~t[1]]) - visited = set() - adj = set(neighbors[state]) - visited - visited.add(state) - for n_state in adj: - for col in colors[n_state]: - clauses.append([expr('~' + state + '_' + col), expr('~' + n_state + '_' + col)]) - return associate('&', map(lambda c: associate('|', c), clauses)) + part = str() + t = str() + for x in neighbors.keys(): + part += '(' + l = 0 + for c in colors[x]: + l += 1 + part += str(x) + '_' + str(c) + t += str(x) + '_' + str(c) + if l != len(colors[x]): + part += ' | ' + t += '|' + part += ') & ' + list = t.split('|') + t = str() + for idx, val in enumerate(list): + for x in list[idx + 1:]: + part += '~(' + val + ' & ' + x + ') & ' + not_part = str() + visit = set() + for x in neighbors.keys(): + adj = set(neighbors[x]) + adj = adj - visit + visit.add(x) + for n in adj: + for col in colors[n]: + not_part += '~(' + str(x) + '_' + str(col) + ' & ' + not_part += str(n) + '_' + str(col) + ') & ' + clause = part + not_part[:len(not_part) - 2] + return expr(clause) australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) From c9c5106bf932067397e31d0b0eb75a4304ef8305 Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 16:53:24 +0200 Subject: [PATCH 10/21] Revert "fixed typo errors and removed unnecessary brackets" This reverts commit f743146c43b28e0525b0f0b332faebc78c15946f. --- logic.py | 11 ++-- tests/test_agents.py | 122 ++++++++++++++++++++----------------------- 2 files changed, 64 insertions(+), 69 deletions(-) diff --git a/logic.py b/logic.py index 0d6c2dca1..066c6e7e2 100644 --- a/logic.py +++ b/logic.py @@ -911,7 +911,7 @@ def new_disjunction(sentences): class WumpusKB(PropKB): """ - Create a Knowledge Base that contains the a temporal "Wumpus physics" and temporal rules with time zero. + Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. """ def __init__(self, dimrow): @@ -1120,7 +1120,8 @@ def set_orientation(self, orientation): self.orientation = orientation def __eq__(self, other): - if other.get_location() == self.get_location() and other.get_orientation() == self.get_orientation(): + if other.get_location() == self.get_location() and \ + other.get_orientation() == self.get_orientation(): return True else: return False @@ -1557,8 +1558,8 @@ def fol_bc_and(KB, goals, theta): P11, P12, P21, P22, P31, B11, B21 = expr('P11, P12, P21, P22, P31, B11, B21') wumpus_kb.tell(~P11) -wumpus_kb.tell(B11 | '<=>' | (P12 | P21)) -wumpus_kb.tell(B21 | '<=>' | (P11 | P22 | P31)) +wumpus_kb.tell(B11 | '<=>' | ((P12 | P21))) +wumpus_kb.tell(B21 | '<=>' | ((P11 | P22 | P31))) wumpus_kb.tell(~B11) wumpus_kb.tell(B21) @@ -1619,7 +1620,7 @@ def diff(y, x): elif op == '/': return (v * diff(u, x) - u * diff(v, x)) / (v * v) elif op == '**' and isnumber(x.op): - return v * u ** (v - 1) * diff(u, x) + return (v * u ** (v - 1) * diff(u, x)) elif op == '**': return (v * u ** (v - 1) * diff(u, x) + u ** v * Expr('log')(u) * diff(v, x)) diff --git a/tests/test_agents.py b/tests/test_agents.py index a5f0f1b22..3c133c32a 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -1,12 +1,12 @@ import random - -from agents import Agent from agents import Direction -from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents, \ - RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ - SimpleReflexAgentProgram, ModelBasedReflexAgentProgram +from agents import Agent +from agents import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ + RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ + SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, rule_match from agents import Wall, Gold, Explorer, Thing, Bump, Glitter, WumpusEnvironment, Pit, \ - VacuumEnvironment, Dirt + VacuumEnvironment, Dirt + random.seed("aima-python") @@ -58,12 +58,12 @@ def test_add(): assert l2.direction == Direction.D -def test_RandomAgentProgram(): - # create a list of all the actions a vacuum cleaner can perform +def test_RandomAgentProgram() : + #create a list of all the actions a vacuum cleaner can perform list = ['Right', 'Left', 'Suck', 'NoOp'] # create a program and then an object of the RandomAgentProgram program = RandomAgentProgram(list) - + agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -72,10 +72,10 @@ def test_RandomAgentProgram(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1, 0): 'Clean' , (0, 0): 'Clean'} -def test_RandomVacuumAgent(): +def test_RandomVacuumAgent() : # create an object of the RandomVacuumAgent agent = RandomVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -85,7 +85,7 @@ def test_RandomVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} def test_TableDrivenAgent(): @@ -109,22 +109,22 @@ def test_TableDrivenAgent(): # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() # initializing some environment status - environment.status = {loc_A: 'Dirty', loc_B: 'Dirty'} + environment.status = {loc_A:'Dirty', loc_B:'Dirty'} # add agent to the environment environment.add_thing(agent) # run the environment by single step everytime to check how environment evolves using TableDrivenAgentProgram - environment.run(steps=1) - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} - environment.run(steps=1) - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} - environment.run(steps=1) - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Clean'} -def test_ReflexVacuumAgent(): +def test_ReflexVacuumAgent() : # create an object of the ReflexVacuumAgent agent = ReflexVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -134,31 +134,31 @@ def test_ReflexVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} def test_SimpleReflexAgentProgram(): class Rule: - + def __init__(self, state, action): self.__state = state self.action = action - + def matches(self, state): return self.__state == state - + loc_A = (0, 0) loc_B = (1, 0) - + # create rules for a two state Vacuum Environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] - + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + def interpret_input(state): return state - + # create a program and then an object of the SimpleReflexAgentProgram - program = SimpleReflexAgentProgram(rules, interpret_input) + program = SimpleReflexAgentProgram(rules, interpret_input) agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -167,7 +167,7 @@ def interpret_input(state): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} def test_ModelBasedReflexAgentProgram(): @@ -185,7 +185,7 @@ def matches(self, state): # create rules for a two-state vacuum environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] def update_state(state, action, percept, model): return percept @@ -203,7 +203,7 @@ def update_state(state, action, percept, model): assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_ModelBasedVacuumAgent(): +def test_ModelBasedVacuumAgent() : # create an object of the ModelBasedVacuumAgent agent = ModelBasedVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -213,10 +213,10 @@ def test_ModelBasedVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} -def test_TableDrivenVacuumAgent(): +def test_TableDrivenVacuumAgent() : # create an object of the TableDrivenVacuumAgent agent = TableDrivenVacuumAgent() # create an object of the TrivialVacuumEnvironment @@ -226,10 +226,10 @@ def test_TableDrivenVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1, 0):'Clean', (0, 0):'Clean'} -def test_compare_agents(): +def test_compare_agents() : environment = TrivialVacuumEnvironment agents = [ModelBasedVacuumAgent, ReflexVacuumAgent] @@ -263,26 +263,24 @@ def test_TableDrivenAgentProgram(): def test_Agent(): def constant_prog(percept): return percept - agent = Agent(constant_prog) result = agent.program(5) assert result == 5 - def test_VacuumEnvironment(): # Initialize Vacuum Environment - v = VacuumEnvironment(6, 6) - # Get an agent + v = VacuumEnvironment(6,6) + #Get an agent agent = ModelBasedVacuumAgent() agent.direction = Direction(Direction.R) v.add_thing(agent) - v.add_thing(Dirt(), location=(2, 1)) + v.add_thing(Dirt(), location=(2,1)) # Check if things are added properly assert len([x for x in v.things if isinstance(x, Wall)]) == 20 assert len([x for x in v.things if isinstance(x, Dirt)]) == 1 - # Let the action begin! + #Let the action begin! assert v.percept(agent) == ("Clean", "None") v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "None") @@ -290,69 +288,65 @@ def test_VacuumEnvironment(): v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "Bump") v.execute_action(agent, "Suck") - assert v.percept(agent) == ("Clean", "None") + assert v.percept(agent) == ("Clean", "None") old_performance = agent.performance v.execute_action(agent, "NoOp") assert old_performance == agent.performance - def test_WumpusEnvironment(): def constant_prog(percept): return percept - # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) - # Check if things are added properly + #Check if things are added properly assert len([x for x in w.things if isinstance(x, Wall)]) == 20 assert any(map(lambda x: isinstance(x, Gold), w.things)) assert any(map(lambda x: isinstance(x, Explorer), w.things)) - assert not any(map(lambda x: not isinstance(x, Thing), w.things)) + assert not any(map(lambda x: not isinstance(x,Thing), w.things)) - # Check that gold and wumpus are not present on (1,1) - assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x, WumpusEnvironment), - w.list_things_at((1, 1)))) + #Check that gold and wumpus are not present on (1,1) + assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x,WumpusEnvironment), + w.list_things_at((1, 1)))) - # Check if w.get_world() segments objects correctly + #Check if w.get_world() segments objects correctly assert len(w.get_world()) == 6 for row in w.get_world(): assert len(row) == 6 - # Start the game! + #Start the game! agent = [x for x in w.things if isinstance(x, Explorer)][0] gold = [x for x in w.things if isinstance(x, Gold)][0] pit = [x for x in w.things if isinstance(x, Pit)][0] - assert w.is_done() == False + assert w.is_done()==False - # Check Walls + #Check Walls agent.location = (1, 2) percepts = w.percept(agent) assert len(percepts) == 5 - assert any(map(lambda x: isinstance(x, Bump), percepts[0])) + assert any(map(lambda x: isinstance(x,Bump), percepts[0])) - # Check Gold + #Check Gold agent.location = gold.location percepts = w.percept(agent) - assert any(map(lambda x: isinstance(x, Glitter), percepts[4])) - agent.location = (gold.location[0], gold.location[1] + 1) + assert any(map(lambda x: isinstance(x,Glitter), percepts[4])) + agent.location = (gold.location[0], gold.location[1]+1) percepts = w.percept(agent) - assert not any(map(lambda x: isinstance(x, Glitter), percepts[4])) + assert not any(map(lambda x: isinstance(x,Glitter), percepts[4])) - # Check agent death + #Check agent death agent.location = pit.location assert w.in_danger(agent) == True assert agent.alive == False assert agent.killed_by == Pit.__name__ assert agent.performance == -1000 - assert w.is_done() == True - + assert w.is_done()==True def test_WumpusEnvironmentActions(): def constant_prog(percept): return percept - # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) @@ -377,4 +371,4 @@ def constant_prog(percept): w.execute_action(agent, 'Climb') assert not any(map(lambda x: isinstance(x, Explorer), w.things)) - assert w.is_done() == True + assert w.is_done()==True \ No newline at end of file From 3243ba1a87d377e8e55619e4872244843889da8e Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 16:53:41 +0200 Subject: [PATCH 11/21] Revert "added map coloring SAT problems" This reverts commit 9e0fa550e85081cf5b92fb6a3418384ab5a9fdfd. --- csp.py | 43 ++++---- logic.py | 241 ++++++++++++++------------------------------ tests/test_csp.py | 28 ++--- tests/test_logic.py | 49 ++++----- 4 files changed, 131 insertions(+), 230 deletions(-) diff --git a/csp.py b/csp.py index c336d7288..4630c49d7 100644 --- a/csp.py +++ b/csp.py @@ -447,7 +447,7 @@ def assign_value(Xj, Xk, csp, assignment): # ______________________________________________________________________________ -# Map Coloring Problems +# Map-Coloring Problems class UniversalDict: @@ -499,26 +499,27 @@ def parse_neighbors(neighbors, variables=None): return dic -australia_csp = MapColoringCSP(list('RGB'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') - -usa_csp = MapColoringCSP(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - -france_csp = MapColoringCSP(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") +australia = MapColoringCSP(list('RGB'), + 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') + +usa = MapColoringCSP(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + +france = MapColoringCSP(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") # ______________________________________________________________________________ diff --git a/logic.py b/logic.py index 066c6e7e2..6aacc4f95 100644 --- a/logic.py +++ b/logic.py @@ -30,7 +30,7 @@ unify Do unification of two FOL sentences diff, simp Symbolic differentiation and simplification """ -from csp import parse_neighbors, UniversalDict + from utils import ( removeall, unique, first, argmax, probability, isnumber, issequence, Expr, expr, subexpressions @@ -42,11 +42,11 @@ import random from collections import defaultdict - # ______________________________________________________________________________ class KB: + """A knowledge base to which you can tell and ask sentences. To create a KB, first subclass this class and implement tell, ask_generator, and retract. Why ask_generator instead of ask? @@ -106,7 +106,6 @@ def retract(self, sentence): if c in self.clauses: self.clauses.remove(c) - # ______________________________________________________________________________ @@ -320,7 +319,6 @@ def pl_true(exp, model={}): else: raise ValueError("illegal operator in logic expression" + str(exp)) - # ______________________________________________________________________________ # Convert to Conjunctive Normal Form (CNF) @@ -370,7 +368,6 @@ def move_not_inwards(s): if s.op == '~': def NOT(b): return move_not_inwards(~b) - a = s.args[0] if a.op == '~': return move_not_inwards(a.args[0]) # ~~A ==> A @@ -448,7 +445,6 @@ def collect(subargs): collect(arg.args) else: result.append(arg) - collect(args) return result @@ -472,7 +468,6 @@ def disjuncts(s): """ return dissociate('|', [s]) - # ______________________________________________________________________________ @@ -486,7 +481,7 @@ def pl_resolution(KB, alpha): while True: n = len(clauses) pairs = [(clauses[i], clauses[j]) - for i in range(n) for j in range(i + 1, n)] + for i in range(n) for j in range(i+1, n)] for (ci, cj) in pairs: resolvents = pl_resolve(ci, cj) if False in resolvents: @@ -510,7 +505,6 @@ def pl_resolve(ci, cj): clauses.append(associate('|', dnew)) return clauses - # ______________________________________________________________________________ @@ -566,6 +560,7 @@ def pl_fc_entails(KB, q): """ wumpus_world_inference = expr("(B11 <=> (P12 | P21)) & ~B11") + """ [Figure 7.16] Propositional Logic Forward Chaining example """ @@ -577,11 +572,9 @@ def pl_fc_entails(KB, q): Definite clauses KB example """ definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', - 'C']: +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: definite_clauses_KB.tell(expr(clause)) - # ______________________________________________________________________________ # DPLL-Satisfiable [Figure 7.17] @@ -672,7 +665,7 @@ def unit_clause_assign(clause, model): if model[sym] == positive: return None, None # clause already True elif P: - return None, None # more than 1 unbound variable + return None, None # more than 1 unbound variable else: P, value = sym, positive return P, value @@ -691,7 +684,6 @@ def inspect_literal(literal): else: return literal, True - # ______________________________________________________________________________ # Walk-SAT [Figure 7.18] @@ -722,186 +714,95 @@ def sat_count(sym): count = len([clause for clause in clauses if pl_true(clause, model)]) model[sym] = not model[sym] return count - sym = argmax(prop_symbols(clause), key=sat_count) model[sym] = not model[sym] # If no solution is found within the flip limit, we return failure return None - -# ______________________________________________________________________________ -# Map Coloring Problems - - -def MapColoringSAT(colors, neighbors): - """Make a SAT for the problem of coloring a map with different colors - for any two adjacent regions. Arguments are a list of colors, and a - dict of {region: [neighbor,...]} entries. This dict may also be - specified as a string of the form defined by parse_neighbors.""" - if isinstance(neighbors, str): - neighbors = parse_neighbors(neighbors) - colors = UniversalDict(colors) - part = str() - t = str() - for x in neighbors.keys(): - part += '(' - l = 0 - for c in colors[x]: - l += 1 - part += str(x) + '_' + str(c) - t += str(x) + '_' + str(c) - if l != len(colors[x]): - part += ' | ' - t += '|' - part += ') & ' - list = t.split('|') - t = str() - for idx, val in enumerate(list): - for x in list[idx + 1:]: - part += '~(' + val + ' & ' + x + ') & ' - not_part = str() - visit = set() - for x in neighbors.keys(): - adj = set(neighbors[x]) - adj = adj - visit - visit.add(x) - for n in adj: - for col in colors[n]: - not_part += '~(' + str(x) + '_' + str(col) + ' & ' - not_part += str(n) + '_' + str(col) + ') & ' - clause = part + not_part[:len(not_part) - 2] - return expr(clause) - - -australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) - -france_sat = MapColoringSAT(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") - -usa_sat = MapColoringSAT(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - - # ______________________________________________________________________________ # Expr functions for WumpusKB and HybridWumpusAgent -def facing_east(time): +def facing_east (time): return Expr('FacingEast', time) - -def facing_west(time): +def facing_west (time): return Expr('FacingWest', time) - -def facing_north(time): +def facing_north (time): return Expr('FacingNorth', time) - -def facing_south(time): +def facing_south (time): return Expr('FacingSouth', time) - -def wumpus(x, y): +def wumpus (x, y): return Expr('W', x, y) - def pit(x, y): return Expr('P', x, y) - def breeze(x, y): return Expr('B', x, y) - def stench(x, y): return Expr('S', x, y) - def wumpus_alive(time): return Expr('WumpusAlive', time) - def have_arrow(time): return Expr('HaveArrow', time) - def percept_stench(time): return Expr('Stench', time) - def percept_breeze(time): return Expr('Breeze', time) - def percept_glitter(time): return Expr('Glitter', time) - def percept_bump(time): return Expr('Bump', time) - def percept_scream(time): return Expr('Scream', time) - def move_forward(time): return Expr('Forward', time) - def shoot(time): return Expr('Shoot', time) - def turn_left(time): return Expr('TurnLeft', time) - def turn_right(time): return Expr('TurnRight', time) - def ok_to_move(x, y, time): return Expr('OK', x, y, time) - -def location(x, y, time=None): +def location(x, y, time = None): if time is None: return Expr('L', x, y) else: return Expr('L', x, y, time) - # Symbols def implies(lhs, rhs): return Expr('==>', lhs, rhs) - def equiv(lhs, rhs): return Expr('<=>', lhs, rhs) - # Helper Function def new_disjunction(sentences): t = sentences[0] - for i in range(1, len(sentences)): + for i in range(1,len(sentences)): t |= sentences[i] return t @@ -914,56 +815,59 @@ class WumpusKB(PropKB): Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. """ - def __init__(self, dimrow): + def __init__(self,dimrow): super().__init__() self.dimrow = dimrow - self.tell(~wumpus(1, 1)) - self.tell(~pit(1, 1)) + self.tell( ~wumpus(1, 1) ) + self.tell( ~pit(1, 1) ) - for y in range(1, dimrow + 1): - for x in range(1, dimrow + 1): + for y in range(1, dimrow+1): + for x in range(1, dimrow+1): pits_in = list() wumpus_in = list() - if x > 1: # West room exists + if x > 1: # West room exists pits_in.append(pit(x - 1, y)) wumpus_in.append(wumpus(x - 1, y)) - if y < dimrow: # North room exists + if y < dimrow: # North room exists pits_in.append(pit(x, y + 1)) wumpus_in.append(wumpus(x, y + 1)) - if x < dimrow: # East room exists + if x < dimrow: # East room exists pits_in.append(pit(x + 1, y)) wumpus_in.append(wumpus(x + 1, y)) - if y > 1: # South room exists + if y > 1: # South room exists pits_in.append(pit(x, y - 1)) wumpus_in.append(wumpus(x, y - 1)) self.tell(equiv(breeze(x, y), new_disjunction(pits_in))) self.tell(equiv(stench(x, y), new_disjunction(wumpus_in))) - # Rule that describes existence of at least one Wumpus + + ## Rule that describes existence of at least one Wumpus wumpus_at_least = list() - for x in range(1, dimrow + 1): + for x in range(1, dimrow+1): for y in range(1, dimrow + 1): wumpus_at_least.append(wumpus(x, y)) self.tell(new_disjunction(wumpus_at_least)) - # Rule that describes existence of at most one Wumpus - for i in range(1, dimrow + 1): - for j in range(1, dimrow + 1): - for u in range(1, dimrow + 1): - for v in range(1, dimrow + 1): - if i != u or j != v: + + ## Rule that describes existence of at most one Wumpus + for i in range(1, dimrow+1): + for j in range(1, dimrow+1): + for u in range(1, dimrow+1): + for v in range(1, dimrow+1): + if i!=u or j!=v: self.tell(~wumpus(i, j) | ~wumpus(u, v)) - # Temporal rules at time zero + + ## Temporal rules at time zero self.tell(location(1, 1, 0)) - for i in range(1, dimrow + 1): + for i in range(1, dimrow+1): for j in range(1, dimrow + 1): self.tell(implies(location(i, j, 0), equiv(percept_breeze(0), breeze(i, j)))) self.tell(implies(location(i, j, 0), equiv(percept_stench(0), stench(i, j)))) @@ -977,6 +881,7 @@ def __init__(self, dimrow): self.tell(~facing_south(0)) self.tell(~facing_west(0)) + def make_action_sentence(self, action, time): actions = [move_forward(time), shoot(time), turn_left(time), turn_right(time)] @@ -990,7 +895,7 @@ def make_percept_sentence(self, percept, time): # Glitter, Bump, Stench, Breeze, Scream flags = [0, 0, 0, 0, 0] - # Things perceived + ## Things perceived if isinstance(percept, Glitter): flags[0] = 1 self.tell(percept_glitter(time)) @@ -1007,7 +912,7 @@ def make_percept_sentence(self, percept, time): flags[4] = 1 self.tell(percept_scream(time)) - # Things not perceived + ## Things not perceived for i in range(len(flags)): if flags[i] == 0: if i == 0: @@ -1021,14 +926,15 @@ def make_percept_sentence(self, percept, time): elif i == 4: self.tell(~percept_scream(time)) + def add_temporal_sentences(self, time): if time == 0: return t = time - 1 - # current location rules - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + ## current location rules + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): self.tell(implies(location(i, j, time), equiv(percept_breeze(time), breeze(i, j)))) self.tell(implies(location(i, j, time), equiv(percept_stench(time), stench(i, j)))) @@ -1050,15 +956,15 @@ def add_temporal_sentences(self, time): if j != self.dimrow: s.append(location(i, j + 1, t) & facing_south(t) & move_forward(t)) - # add sentence about location i,j + ## add sentence about location i,j self.tell(new_disjunction(s)) - # add sentence about safety of location i,j + ## add sentence about safety of location i,j self.tell( equiv(ok_to_move(i, j, time), ~pit(i, j) & ~wumpus(i, j) & wumpus_alive(time)) ) - # Rules about current orientation + ## Rules about current orientation a = facing_north(t) & turn_right(t) b = facing_south(t) & turn_left(t) @@ -1084,15 +990,16 @@ def add_temporal_sentences(self, time): s = equiv(facing_south(time), a | b | c) self.tell(s) - # Rules about last action + ## Rules about last action self.tell(equiv(move_forward(t), ~turn_right(t) & ~turn_left(t))) - # Rule about the arrow + ##Rule about the arrow self.tell(equiv(have_arrow(time), have_arrow(t) & ~shoot(t))) - # Rule about Wumpus (dead or alive) + ##Rule about Wumpus (dead or alive) self.tell(equiv(wumpus_alive(time), wumpus_alive(t) & ~percept_scream(time))) + def ask_if_true(self, query): return pl_resolution(self, query) @@ -1100,12 +1007,13 @@ def ask_if_true(self, query): # ______________________________________________________________________________ -class WumpusPosition: +class WumpusPosition(): def __init__(self, x, y, orientation): self.X = x self.Y = y self.orientation = orientation + def get_location(self): return self.X, self.Y @@ -1121,19 +1029,18 @@ def set_orientation(self, orientation): def __eq__(self, other): if other.get_location() == self.get_location() and \ - other.get_orientation() == self.get_orientation(): + other.get_orientation()==self.get_orientation(): return True else: return False - # ______________________________________________________________________________ class HybridWumpusAgent(Agent): """An agent for the wumpus world that does logical inference. [Figure 7.20]""" - def __init__(self, dimentions): + def __init__(self,dimentions): self.dimrow = dimentions self.kb = WumpusKB(self.dimrow) self.t = 0 @@ -1141,14 +1048,15 @@ def __init__(self, dimentions): self.current_position = WumpusPosition(1, 1, 'UP') super().__init__(self.execute) + def execute(self, percept): self.kb.make_percept_sentence(percept, self.t) self.kb.add_temporal_sentences(self.t) temp = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if self.kb.ask_if_true(location(i, j, self.t)): temp.append(i) temp.append(j) @@ -1163,8 +1071,8 @@ def execute(self, percept): self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT') safe_points = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if self.kb.ask_if_true(ok_to_move(i, j, self.t)): safe_points.append([i, j]) @@ -1172,14 +1080,14 @@ def execute(self, percept): goals = list() goals.append([1, 1]) self.plan.append('Grab') - actions = self.plan_route(self.current_position, goals, safe_points) + actions = self.plan_route(self.current_position,goals,safe_points) self.plan.extend(actions) self.plan.append('Climb') if len(self.plan) == 0: unvisited = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): for k in range(self.t): if self.kb.ask_if_true(location(i, j, k)): unvisited.append([i, j]) @@ -1189,13 +1097,13 @@ def execute(self, percept): if u not in unvisited_and_safe and s == u: unvisited_and_safe.append(u) - temp = self.plan_route(self.current_position, unvisited_and_safe, safe_points) + temp = self.plan_route(self.current_position,unvisited_and_safe,safe_points) self.plan.extend(temp) if len(self.plan) == 0 and self.kb.ask_if_true(have_arrow(self.t)): possible_wumpus = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if not self.kb.ask_if_true(wumpus(i, j)): possible_wumpus.append([i, j]) @@ -1204,8 +1112,8 @@ def execute(self, percept): if len(self.plan) == 0: not_unsafe = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if not self.kb.ask_if_true(ok_to_move(i, j, self.t)): not_unsafe.append([i, j]) temp = self.plan_route(self.current_position, not_unsafe, safe_points) @@ -1225,17 +1133,19 @@ def execute(self, percept): return action + def plan_route(self, current, goals, allowed): problem = PlanRoute(current, goals, allowed, self.dimrow) return astar_search(problem).solution() + def plan_shot(self, current, goals, allowed): shooting_positions = set() for loc in goals: x = loc[0] y = loc[1] - for i in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): if i < x: shooting_positions.add(WumpusPosition(i, y, 'EAST')) if i > x: @@ -1247,7 +1157,7 @@ def plan_shot(self, current, goals, allowed): # Can't have a shooting position from any of the rooms the Wumpus could reside orientations = ['EAST', 'WEST', 'NORTH', 'SOUTH'] - for loc in goals: + for loc in goals: for orientation in orientations: shooting_positions.remove(WumpusPosition(loc[0], loc[1], orientation)) @@ -1276,7 +1186,7 @@ def translate_to_SAT(init, transition, goal, time): # Symbol claiming state s at time t state_counter = itertools.count() for s in states: - for t in range(time + 1): + for t in range(time+1): state_sym[s, t] = Expr("State_{}".format(next(state_counter))) # Add initial state axiom @@ -1296,11 +1206,11 @@ def translate_to_SAT(init, transition, goal, time): "Transition_{}".format(next(transition_counter))) # Change the state from s to s_ - clauses.append(action_sym[s, action, t] | '==>' | state_sym[s, t]) - clauses.append(action_sym[s, action, t] | '==>' | state_sym[s_, t + 1]) + clauses.append(action_sym[s, action, t] |'==>'| state_sym[s, t]) + clauses.append(action_sym[s, action, t] |'==>'| state_sym[s_, t + 1]) # Allow only one state at any time - for t in range(time + 1): + for t in range(time+1): # must be a state at any time clauses.append(associate('|', [state_sym[s, t] for s in states])) @@ -1453,7 +1363,6 @@ def standardize_variables(sentence, dic=None): standardize_variables.counter = itertools.count() - # ______________________________________________________________________________ @@ -1495,7 +1404,6 @@ def fol_fc_ask(KB, alpha): """A simple forward-chaining algorithm. [Figure 9.3]""" # TODO: Improve efficiency kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)}) - def enum_subst(p): query_vars = list({v for clause in p for v in variables(clause)}) for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)): @@ -1589,7 +1497,6 @@ def fol_bc_and(KB, goals, theta): 'Enemy(Nono, America)' ])) - # ______________________________________________________________________________ # Example application (not in the book). diff --git a/tests/test_csp.py b/tests/test_csp.py index ca4075be8..269d0848f 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -10,16 +10,16 @@ def test_csp_assign(): var = 10 val = 5 assignment = {} - australia_csp.assign(var, val, assignment) + australia.assign(var, val, assignment) - assert australia_csp.nassigns == 1 + assert australia.nassigns == 1 assert assignment[var] == val def test_csp_unassign(): var = 10 assignment = {var: 5} - australia_csp.unassign(var, assignment) + australia.unassign(var, assignment) assert var not in assignment @@ -356,22 +356,22 @@ def test_forward_checking(): def test_backtracking_search(): - assert backtracking_search(australia_csp) - assert backtracking_search(australia_csp, select_unassigned_variable=mrv) - assert backtracking_search(australia_csp, order_domain_values=lcv) - assert backtracking_search(australia_csp, select_unassigned_variable=mrv, + assert backtracking_search(australia) + assert backtracking_search(australia, select_unassigned_variable=mrv) + assert backtracking_search(australia, order_domain_values=lcv) + assert backtracking_search(australia, select_unassigned_variable=mrv, order_domain_values=lcv) - assert backtracking_search(australia_csp, inference=forward_checking) - assert backtracking_search(australia_csp, inference=mac) - assert backtracking_search(usa_csp, select_unassigned_variable=mrv, + assert backtracking_search(australia, inference=forward_checking) + assert backtracking_search(australia, inference=mac) + assert backtracking_search(usa, select_unassigned_variable=mrv, order_domain_values=lcv, inference=mac) def test_min_conflicts(): - assert min_conflicts(australia_csp) - assert min_conflicts(france_csp) + assert min_conflicts(australia) + assert min_conflicts(france) - tests = [(usa_csp, None)] * 3 + tests = [(usa, None)] * 3 assert failure_test(min_conflicts, tests) >= 1 / 3 australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') @@ -444,7 +444,7 @@ def test_parse_neighbours(): def test_topological_sort(): root = 'NT' - Sort, Parents = topological_sort(australia_csp, root) + Sort, Parents = topological_sort(australia, root) assert Sort == ['NT', 'SA', 'Q', 'NSW', 'V', 'WA'] assert Parents['NT'] == None diff --git a/tests/test_logic.py b/tests/test_logic.py index a2ac8c080..378f1f0fc 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -3,9 +3,8 @@ from utils import expr_handle_infix_ops, count, Symbol definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', - 'C']: - definite_clauses_KB.tell(expr(clause)) +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: + definite_clauses_KB.tell(expr(clause)) def test_is_symbol(): @@ -48,7 +47,7 @@ def test_extend(): def test_subst(): - assert subst({x: 42, y: 0}, F(x) + y) == (F(42) + 0) + assert subst({x: 42, y:0}, F(x) + y) == (F(42) + 0) def test_PropKB(): @@ -56,7 +55,7 @@ def test_PropKB(): assert count(kb.ask(expr) for expr in [A, C, D, E, Q]) is 0 kb.tell(A & E) assert kb.ask(A) == kb.ask(E) == {} - kb.tell(E | '==>' | C) + kb.tell(E |'==>'| C) assert kb.ask(C) == {} kb.retract(E) assert kb.ask(E) is False @@ -95,8 +94,7 @@ def test_is_definite_clause(): def test_parse_definite_clause(): assert parse_definite_clause(expr('A & B & C & D ==> E')) == ([A, B, C, D], E) assert parse_definite_clause(expr('Farmer(Mac)')) == ([], expr('Farmer(Mac)')) - assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ( - [expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) + assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ([expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) def test_pl_true(): @@ -133,28 +131,28 @@ def test_dpll(): assert (dpll_satisfiable(A & ~B & C & (A | ~D) & (~E | ~D) & (C | ~D) & (~A | ~F) & (E | ~F) & (~D | ~F) & (B | ~C | D) & (A | ~E | F) & (~A | E | D)) == {B: False, C: True, A: True, F: False, D: True, E: False}) - assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} - assert dpll_satisfiable((A | (B & C)) | '<=>' | ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} - assert dpll_satisfiable(A | '<=>' | B) == {A: True, B: True} + assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} + assert dpll_satisfiable((A | (B & C)) |'<=>'| ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} + assert dpll_satisfiable(A |'<=>'| B) == {A: True, B: True} assert dpll_satisfiable(A & ~B) == {A: True, B: False} assert dpll_satisfiable(P & ~P) is False def test_find_pure_symbol(): - assert find_pure_symbol([A, B, C], [A | ~B, ~B | ~C, C | A]) == (A, True) - assert find_pure_symbol([A, B, C], [~A | ~B, ~B | ~C, C | A]) == (B, False) - assert find_pure_symbol([A, B, C], [~A | B, ~B | ~C, C | A]) == (None, None) + assert find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A]) == (A, True) + assert find_pure_symbol([A, B, C], [~A|~B,~B|~C,C|A]) == (B, False) + assert find_pure_symbol([A, B, C], [~A|B,~B|~C,C|A]) == (None, None) def test_unit_clause_assign(): - assert unit_clause_assign(A | B | C, {A: True}) == (None, None) - assert unit_clause_assign(B | C, {A: True}) == (None, None) - assert unit_clause_assign(B | ~A, {A: True}) == (B, True) + assert unit_clause_assign(A|B|C, {A:True}) == (None, None) + assert unit_clause_assign(B|C, {A:True}) == (None, None) + assert unit_clause_assign(B|~A, {A:True}) == (B, True) def test_find_unit_clause(): - assert find_unit_clause([A | B | C, B | ~C, ~A | ~B], {A: True}) == (B, False) - + assert find_unit_clause([A|B|C, B|~C, ~A|~B], {A:True}) == (B, False) + def test_unify(): assert unify(x, x, {}) == {} @@ -177,9 +175,9 @@ def test_tt_entails(): assert tt_entails(P & Q, Q) assert not tt_entails(P | Q, Q) assert tt_entails(A & (B | C) & E & F & ~(P | Q), A & E & F & ~P & ~Q) - assert not tt_entails(P | '<=>' | Q, Q) - assert tt_entails((P | '==>' | Q) & P, Q) - assert not tt_entails((P | '<=>' | Q) & ~P, Q) + assert not tt_entails(P |'<=>'| Q, Q) + assert tt_entails((P |'==>'| Q) & P, Q) + assert not tt_entails((P |'<=>'| Q) & ~P, Q) def test_prop_symbols(): @@ -233,13 +231,12 @@ def test_move_not_inwards(): def test_distribute_and_over_or(): - def test_entailment(s, has_and=False): + def test_entailment(s, has_and = False): result = distribute_and_over_or(s) if has_and: assert result.op == '&' assert tt_entails(s, result) assert tt_entails(result, s) - test_entailment((A & B) | C, True) test_entailment((A | B) & C, True) test_entailment((A | B) | C, False) @@ -256,8 +253,7 @@ def test_to_cnf(): assert repr(to_cnf("a | (b & c) | d")) == '((b | a | d) & (c | a | d))' assert repr(to_cnf("A & (B | (D & E))")) == '(A & (D | B) & (E | B))' assert repr(to_cnf("A | (B | (C | (D & E)))")) == '((D | A | B | C) & (E | A | B | C))' - assert repr(to_cnf( - '(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' + assert repr(to_cnf('(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' def test_pl_resolution(): @@ -285,7 +281,6 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) - assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]' @@ -300,7 +295,6 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) - assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' assert repr(test_ask('Enemy(x, America)', crime_kb)) == '[{x: Nono}]' assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' @@ -322,7 +316,6 @@ def check_SAT(clauses, single_solution={}): if single_solution: # Cross check the solution if only one exists assert all(pl_true(x, single_solution) for x in clauses) assert soln == single_solution - # Test WalkSat for problems with solution check_SAT([A & B, A & C]) check_SAT([A | B, P & Q, P & B]) From 2af16590b7e4fe3df568a19aca09d91dc01862fb Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 18:22:39 +0200 Subject: [PATCH 12/21] Revert "removed useless doctest for AC4 in Sudoku because AC4's tests are already present in test_csp.py" This reverts commit b3cd24c511a82275f5b43c9f176396e6ba05f67e. --- csp.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/csp.py b/csp.py index 4630c49d7..f2235091d 100644 --- a/csp.py +++ b/csp.py @@ -673,6 +673,36 @@ class Sudoku(CSP): >>> h = Sudoku(harder1) >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None True + + >>> e = Sudoku(easy1) + >>> e.display(e.infer_assignment()) + . . 3 | . 2 . | 6 . . + 9 . . | 3 . 5 | . . 1 + . . 1 | 8 . 6 | 4 . . + ------+-------+------ + . . 8 | 1 . 2 | 9 . . + 7 . . | . . . | . . 8 + . . 6 | 7 . 8 | 2 . . + ------+-------+------ + . . 2 | 6 . 9 | 5 . . + 8 . . | 2 . 3 | . . 9 + . . 5 | . 1 . | 3 . . + >>> AC4(e); e.display(e.infer_assignment()) + True + 4 8 3 | 9 2 1 | 6 5 7 + 9 6 7 | 3 4 5 | 8 2 1 + 2 5 1 | 8 7 6 | 4 9 3 + ------+-------+------ + 5 4 8 | 1 3 2 | 9 7 6 + 7 2 9 | 5 6 4 | 1 3 8 + 1 3 6 | 7 9 8 | 2 4 5 + ------+-------+------ + 3 7 2 | 6 8 9 | 5 1 4 + 8 1 4 | 2 5 3 | 7 6 9 + 6 9 5 | 4 1 7 | 3 8 2 + >>> h = Sudoku(harder1) + >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None + True """ R3 = _R3 From 0c7e5af00d33a8c11f928ece91a3c125bcf42bb6 Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 18:22:55 +0200 Subject: [PATCH 13/21] Revert "added doctest in Sudoku for AC4 and and the possibility of choosing the constant propagation algorithm in mac inference" This reverts commit 6986247481a05f1e558b93b2bf3cdae395f9c4ee. --- csp.py | 44 +++++++------------------------------------- tests/test_csp.py | 8 ++++---- 2 files changed, 11 insertions(+), 41 deletions(-) diff --git a/csp.py b/csp.py index f2235091d..7a58ca19d 100644 --- a/csp.py +++ b/csp.py @@ -290,9 +290,9 @@ def forward_checking(csp, var, value, assignment, removals): return True -def mac(csp, var, value, assignment, removals, constraint_propagation=AC3): +def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" - return constraint_propagation(csp, {(X, var) for X in csp.neighbors[var]}, removals) + return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) # The search, proper @@ -326,11 +326,11 @@ def backtrack(assignment): # ______________________________________________________________________________ -# Min-conflicts Hill Climbing search for CSPs +# Min-conflicts hillclimbing search for CSPs def min_conflicts(csp, max_steps=100000): - """Solve a CSP by stochastic Hill Climbing on the number of conflicts.""" + """Solve a CSP by stochastic hillclimbing on the number of conflicts.""" # Generate a complete assignment for all variables (probably with conflicts) csp.current = current = {} for var in csp.variables: @@ -532,7 +532,7 @@ def queen_constraint(A, a, B, b): return A == B or (a != b and A + a != B + b and A - a != B - b) -class NQueens(CSP): +class NQueensCSP(CSP): """Make a CSP for the nQueens problem for search with min_conflicts. Suitable for large n, it uses only data structures of size O(n). Think of placing queens one per column, from left to right. @@ -548,7 +548,7 @@ class NQueens(CSP): a variable, and a best value for the variable, are each O(n). If you want, you can keep track of conflicted variables, then variable selection will also be O(1). - >>> len(backtracking_search(NQueens(8))) + >>> len(backtracking_search(NQueensCSP(8))) 8 """ @@ -673,37 +673,7 @@ class Sudoku(CSP): >>> h = Sudoku(harder1) >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None True - - >>> e = Sudoku(easy1) - >>> e.display(e.infer_assignment()) - . . 3 | . 2 . | 6 . . - 9 . . | 3 . 5 | . . 1 - . . 1 | 8 . 6 | 4 . . - ------+-------+------ - . . 8 | 1 . 2 | 9 . . - 7 . . | . . . | . . 8 - . . 6 | 7 . 8 | 2 . . - ------+-------+------ - . . 2 | 6 . 9 | 5 . . - 8 . . | 2 . 3 | . . 9 - . . 5 | . 1 . | 3 . . - >>> AC4(e); e.display(e.infer_assignment()) - True - 4 8 3 | 9 2 1 | 6 5 7 - 9 6 7 | 3 4 5 | 8 2 1 - 2 5 1 | 8 7 6 | 4 9 3 - ------+-------+------ - 5 4 8 | 1 3 2 | 9 7 6 - 7 2 9 | 5 6 4 | 1 3 8 - 1 3 6 | 7 9 8 | 2 4 5 - ------+-------+------ - 3 7 2 | 6 8 9 | 5 1 4 - 8 1 4 | 2 5 3 | 7 6 9 - 6 9 5 | 4 1 7 | 3 8 2 - >>> h = Sudoku(harder1) - >>> backtracking_search(h, select_unassigned_variable=mrv, inference=forward_checking) is not None - True - """ + """ # noqa R3 = _R3 Cell = _CELL diff --git a/tests/test_csp.py b/tests/test_csp.py index 269d0848f..02852b4f2 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -376,12 +376,12 @@ def test_min_conflicts(): australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') assert min_conflicts(australia_impossible, 1000) is None - assert min_conflicts(NQueens(2), 1000) is None - assert min_conflicts(NQueens(3), 1000) is None + assert min_conflicts(NQueensCSP(2), 1000) is None + assert min_conflicts(NQueensCSP(3), 1000) is None def test_nqueens_csp(): - csp = NQueens(8) + csp = NQueensCSP(8) assignment = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} csp.assign(5, 5, assignment) @@ -428,7 +428,7 @@ def test_nqueens_csp(): assert 6 not in assignment for n in range(5, 9): - csp = NQueens(n) + csp = NQueensCSP(n) solution = min_conflicts(csp) assert not solution or sorted(solution.values()) == list(range(n)) From ff8c411c6f009bcd47af7ad938b363dbab29c31f Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 18:24:23 +0200 Subject: [PATCH 14/21] Revert "added the mentioned AC4 algorithm for constraint propagation" This reverts commit 03551fbf2aa3980b915d4b6fefcbc70f24547b03. --- csp.py | 79 ++++++----------------------------------------- tests/test_csp.py | 28 +---------------- 2 files changed, 11 insertions(+), 96 deletions(-) diff --git a/csp.py b/csp.py index 7a58ca19d..ee59d4a6b 100644 --- a/csp.py +++ b/csp.py @@ -3,7 +3,7 @@ from utils import argmin_random_tie, count, first import search -from collections import defaultdict, Counter +from collections import defaultdict from functools import reduce import itertools @@ -50,12 +50,13 @@ class CSP(search.Problem): def __init__(self, variables, domains, neighbors, constraints): """Construct a CSP problem. If variables is empty, it becomes domains.keys().""" - super().__init__(()) variables = variables or list(domains.keys()) + self.variables = variables self.domains = domains self.neighbors = neighbors self.constraints = constraints + self.initial = () self.curr_domains = None self.nassigns = 0 @@ -73,12 +74,10 @@ def unassign(self, var, assignment): def nconflicts(self, var, val, assignment): """Return the number of conflicts var=val has with other variables.""" - # Subclasses may implement this more efficiently def conflict(var2): return (var2 in assignment and not self.constraints(var, val, var2, assignment[var2])) - return count(conflict(v) for v in self.neighbors[var]) def display(self, assignment): @@ -154,7 +153,6 @@ def conflicted_vars(self, current): return [var for var in self.variables if self.nconflicts(var, current[var], current) > 0] - # ______________________________________________________________________________ # Constraint Propagation with AC-3 @@ -185,51 +183,6 @@ def revise(csp, Xi, Xj, removals): revised = True return revised - -# Constraint Propagation with AC-4 - -def AC4(csp, queue=None, removals=None): - """AC4 algorithm runs in O(cd^2) worst-case time but can be slower - than AC3 on average cases""" - if queue is None: - queue = {(Xi, Xk) for Xi in csp.variables for Xk in csp.neighbors[Xi]} - csp.support_pruning() - support_counter = Counter() - variable_value_pairs_supported = defaultdict(set) - unsupported_variable_value_pairs = [] - # construction and initialization of support sets - while queue: - (Xi, Xj) = queue.pop() - revised = False - for x in csp.curr_domains[Xi][:]: - for y in csp.curr_domains[Xj]: - if csp.constraints(Xi, x, Xj, y): - support_counter[(Xi, x, Xj)] += 1 - variable_value_pairs_supported[(Xj, y)].add((Xi, x)) - if support_counter[(Xi, x, Xj)] == 0: - csp.prune(Xi, x, removals) - revised = True - unsupported_variable_value_pairs.append((Xi, x)) - if revised: - if not csp.curr_domains[Xi]: - return False - # propagation of removed values - while unsupported_variable_value_pairs: - Xj, y = unsupported_variable_value_pairs.pop() - for Xi, x in variable_value_pairs_supported[(Xj, y)]: - revised = False - if x in csp.curr_domains[Xi][:]: - support_counter[(Xi, x, Xj)] -= 1 - if support_counter[(Xi, x, Xj)] == 0: - csp.prune(Xi, x, removals) - revised = True - unsupported_variable_value_pairs.append((Xi, x)) - if revised: - if not csp.curr_domains[Xi]: - return False - return True - - # ______________________________________________________________________________ # CSP Backtracking Search @@ -255,7 +208,6 @@ def num_legal_values(csp, var, assignment): return count(csp.nconflicts(var, val, assignment) == 0 for val in csp.domains[var]) - # Value ordering @@ -269,7 +221,6 @@ def lcv(var, assignment, csp): return sorted(csp.choices(var), key=lambda val: csp.nconflicts(var, val, assignment)) - # Inference @@ -294,7 +245,6 @@ def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) - # The search, proper @@ -324,7 +274,6 @@ def backtrack(assignment): assert result is None or csp.goal_test(result) return result - # ______________________________________________________________________________ # Min-conflicts hillclimbing search for CSPs @@ -353,7 +302,6 @@ def min_conflicts_value(csp, var, current): return argmin_random_tie(csp.domains[var], key=lambda val: csp.nconflicts(var, val, current)) - # ______________________________________________________________________________ @@ -408,7 +356,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents): visited[node] = True for n in neighbors[node]: - if (not visited[n]): + if(not visited[n]): build_topological(n, node, neighbors, visited, stack, parents) parents[node] = parent @@ -418,9 +366,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents): def make_arc_consistent(Xj, Xk, csp): """Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints, by removing the possible values of Xj that cause inconsistencies.""" - # csp.curr_domains[Xj] = [] + #csp.curr_domains[Xj] = [] for val1 in csp.domains[Xj]: - keep = False # Keep or remove val1 + keep = False # Keep or remove val1 for val2 in csp.domains[Xk]: if csp.constraints(Xj, val1, Xk, val2): # Found a consistent assignment for val1, keep it @@ -445,7 +393,6 @@ def assign_value(Xj, Xk, csp, assignment): # No consistent assignment available return None - # ______________________________________________________________________________ # Map-Coloring Problems @@ -521,7 +468,6 @@ def parse_neighbors(neighbors, variables=None): PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: AU BO FC PA LR""") - # ______________________________________________________________________________ # n-Queens Problem @@ -557,16 +503,16 @@ def __init__(self, n): CSP.__init__(self, list(range(n)), UniversalDict(list(range(n))), UniversalDict(list(range(n))), queen_constraint) - self.rows = [0] * n - self.ups = [0] * (2 * n - 1) - self.downs = [0] * (2 * n - 1) + self.rows = [0]*n + self.ups = [0]*(2*n - 1) + self.downs = [0]*(2*n - 1) def nconflicts(self, var, val, assignment): """The number of conflicts, as recorded with each assignment. Count conflicts in row and in up, down diagonals. If there is a queen there, it can't conflict with itself, so subtract 3.""" n = len(self.variables) - c = self.rows[val] + self.downs[var + val] + self.ups[var - val + n - 1] + c = self.rows[val] + self.downs[var+val] + self.ups[var-val+n-1] if assignment.get(var, None) == val: c -= 3 return c @@ -614,7 +560,6 @@ def display(self, assignment): print(str(self.nconflicts(var, val, assignment)) + ch, end=' ') print() - # ______________________________________________________________________________ # Sudoku @@ -701,12 +646,9 @@ def show_cell(cell): return str(assignment.get(cell, '.')) def abut(lines1, lines2): return list( map(' | '.join, list(zip(lines1, lines2)))) - print('\n------+-------+------\n'.join( '\n'.join(reduce( abut, map(show_box, brow))) for brow in self.bgrid)) - - # ______________________________________________________________________________ # The Zebra Puzzle @@ -774,7 +716,6 @@ def zebra_constraint(A, a, B, b, recurse=0): (A in Smokes and B in Smokes)): return not same raise Exception('error') - return CSP(variables, domains, neighbors, zebra_constraint) diff --git a/tests/test_csp.py b/tests/test_csp.py index 02852b4f2..77b35c796 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -211,39 +211,13 @@ def test_AC3(): removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) domains = {'A': [2, 4], 'B': [3, 5]} - constraints = lambda X, x, Y, y: x > y + constraints = lambda X, x, Y, y: int(x) > int(y) removals = [] csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) assert AC3(csp, removals=removals) -def test_AC4(): - neighbors = parse_neighbors('A: B; B: ') - domains = {'A': [0, 1, 2, 3, 4], 'B': [0, 1, 2, 3, 4]} - constraints = lambda X, x, Y, y: x % 2 == 0 and (x + y) == 4 and y % 2 != 0 - removals = [] - - csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) - - assert AC4(csp, removals=removals) is False - - constraints = lambda X, x, Y, y: (x % 2) == 0 and (x + y) == 4 - removals = [] - csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) - - assert AC4(csp, removals=removals) is True - assert (removals == [('A', 1), ('A', 3), ('B', 1), ('B', 3)] or - removals == [('B', 1), ('B', 3), ('A', 1), ('A', 3)]) - - domains = {'A': [2, 4], 'B': [3, 5]} - constraints = lambda X, x, Y, y: (X == 'A' and Y == 'B') or (X == 'B' and Y == 'A') and x < y - removals = [] - csp = CSP(variables=None, domains=domains, neighbors=neighbors, constraints=constraints) - - assert AC4(csp, removals=removals) - - def test_first_unassigned_variable(): map_coloring_test = MapColoringCSP(list('123'), 'A: B C; B: C; C: ') assignment = {'A': '1', 'B': '2'} From 93af259e4811ddd775429f8a334111b9dd9e268c Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 18:44:17 +0200 Subject: [PATCH 15/21] added map coloring SAT problem --- csp.py | 73 ++++++++------ logic.py | 233 +++++++++++++++++++++++++++++--------------- tests/test_csp.py | 28 +++--- tests/test_logic.py | 52 +++++----- 4 files changed, 242 insertions(+), 144 deletions(-) diff --git a/csp.py b/csp.py index ee59d4a6b..4c1203f4a 100644 --- a/csp.py +++ b/csp.py @@ -74,10 +74,12 @@ def unassign(self, var, assignment): def nconflicts(self, var, val, assignment): """Return the number of conflicts var=val has with other variables.""" + # Subclasses may implement this more efficiently def conflict(var2): return (var2 in assignment and not self.constraints(var, val, var2, assignment[var2])) + return count(conflict(v) for v in self.neighbors[var]) def display(self, assignment): @@ -153,6 +155,7 @@ def conflicted_vars(self, current): return [var for var in self.variables if self.nconflicts(var, current[var], current) > 0] + # ______________________________________________________________________________ # Constraint Propagation with AC-3 @@ -183,6 +186,7 @@ def revise(csp, Xi, Xj, removals): revised = True return revised + # ______________________________________________________________________________ # CSP Backtracking Search @@ -208,6 +212,7 @@ def num_legal_values(csp, var, assignment): return count(csp.nconflicts(var, val, assignment) == 0 for val in csp.domains[var]) + # Value ordering @@ -221,6 +226,7 @@ def lcv(var, assignment, csp): return sorted(csp.choices(var), key=lambda val: csp.nconflicts(var, val, assignment)) + # Inference @@ -245,6 +251,7 @@ def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) + # The search, proper @@ -274,6 +281,7 @@ def backtrack(assignment): assert result is None or csp.goal_test(result) return result + # ______________________________________________________________________________ # Min-conflicts hillclimbing search for CSPs @@ -302,6 +310,7 @@ def min_conflicts_value(csp, var, current): return argmin_random_tie(csp.domains[var], key=lambda val: csp.nconflicts(var, val, current)) + # ______________________________________________________________________________ @@ -356,7 +365,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents): visited[node] = True for n in neighbors[node]: - if(not visited[n]): + if (not visited[n]): build_topological(n, node, neighbors, visited, stack, parents) parents[node] = parent @@ -366,9 +375,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents): def make_arc_consistent(Xj, Xk, csp): """Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints, by removing the possible values of Xj that cause inconsistencies.""" - #csp.curr_domains[Xj] = [] + # csp.curr_domains[Xj] = [] for val1 in csp.domains[Xj]: - keep = False # Keep or remove val1 + keep = False # Keep or remove val1 for val2 in csp.domains[Xk]: if csp.constraints(Xj, val1, Xk, val2): # Found a consistent assignment for val1, keep it @@ -393,8 +402,9 @@ def assign_value(Xj, Xk, csp, assignment): # No consistent assignment available return None + # ______________________________________________________________________________ -# Map-Coloring Problems +# Map Coloring Problems class UniversalDict: @@ -446,27 +456,27 @@ def parse_neighbors(neighbors, variables=None): return dic -australia = MapColoringCSP(list('RGB'), - 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') - -usa = MapColoringCSP(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - -france = MapColoringCSP(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") +australia_csp = MapColoringCSP(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) + +usa_csp = MapColoringCSP(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + +france_csp = MapColoringCSP(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") + # ______________________________________________________________________________ # n-Queens Problem @@ -503,16 +513,16 @@ def __init__(self, n): CSP.__init__(self, list(range(n)), UniversalDict(list(range(n))), UniversalDict(list(range(n))), queen_constraint) - self.rows = [0]*n - self.ups = [0]*(2*n - 1) - self.downs = [0]*(2*n - 1) + self.rows = [0] * n + self.ups = [0] * (2 * n - 1) + self.downs = [0] * (2 * n - 1) def nconflicts(self, var, val, assignment): """The number of conflicts, as recorded with each assignment. Count conflicts in row and in up, down diagonals. If there is a queen there, it can't conflict with itself, so subtract 3.""" n = len(self.variables) - c = self.rows[val] + self.downs[var+val] + self.ups[var-val+n-1] + c = self.rows[val] + self.downs[var + val] + self.ups[var - val + n - 1] if assignment.get(var, None) == val: c -= 3 return c @@ -560,6 +570,7 @@ def display(self, assignment): print(str(self.nconflicts(var, val, assignment)) + ch, end=' ') print() + # ______________________________________________________________________________ # Sudoku @@ -646,9 +657,12 @@ def show_cell(cell): return str(assignment.get(cell, '.')) def abut(lines1, lines2): return list( map(' | '.join, list(zip(lines1, lines2)))) + print('\n------+-------+------\n'.join( '\n'.join(reduce( abut, map(show_box, brow))) for brow in self.bgrid)) + + # ______________________________________________________________________________ # The Zebra Puzzle @@ -716,6 +730,7 @@ def zebra_constraint(A, a, B, b, recurse=0): (A in Smokes and B in Smokes)): return not same raise Exception('error') + return CSP(variables, domains, neighbors, zebra_constraint) diff --git a/logic.py b/logic.py index 6aacc4f95..5ef29212f 100644 --- a/logic.py +++ b/logic.py @@ -30,7 +30,7 @@ unify Do unification of two FOL sentences diff, simp Symbolic differentiation and simplification """ - +from csp import parse_neighbors, UniversalDict from utils import ( removeall, unique, first, argmax, probability, isnumber, issequence, Expr, expr, subexpressions @@ -42,11 +42,11 @@ import random from collections import defaultdict + # ______________________________________________________________________________ class KB: - """A knowledge base to which you can tell and ask sentences. To create a KB, first subclass this class and implement tell, ask_generator, and retract. Why ask_generator instead of ask? @@ -106,6 +106,7 @@ def retract(self, sentence): if c in self.clauses: self.clauses.remove(c) + # ______________________________________________________________________________ @@ -319,6 +320,7 @@ def pl_true(exp, model={}): else: raise ValueError("illegal operator in logic expression" + str(exp)) + # ______________________________________________________________________________ # Convert to Conjunctive Normal Form (CNF) @@ -368,6 +370,7 @@ def move_not_inwards(s): if s.op == '~': def NOT(b): return move_not_inwards(~b) + a = s.args[0] if a.op == '~': return move_not_inwards(a.args[0]) # ~~A ==> A @@ -445,6 +448,7 @@ def collect(subargs): collect(arg.args) else: result.append(arg) + collect(args) return result @@ -468,6 +472,7 @@ def disjuncts(s): """ return dissociate('|', [s]) + # ______________________________________________________________________________ @@ -481,7 +486,7 @@ def pl_resolution(KB, alpha): while True: n = len(clauses) pairs = [(clauses[i], clauses[j]) - for i in range(n) for j in range(i+1, n)] + for i in range(n) for j in range(i + 1, n)] for (ci, cj) in pairs: resolvents = pl_resolve(ci, cj) if False in resolvents: @@ -505,6 +510,7 @@ def pl_resolve(ci, cj): clauses.append(associate('|', dnew)) return clauses + # ______________________________________________________________________________ @@ -560,7 +566,6 @@ def pl_fc_entails(KB, q): """ wumpus_world_inference = expr("(B11 <=> (P12 | P21)) & ~B11") - """ [Figure 7.16] Propositional Logic Forward Chaining example """ @@ -572,9 +577,11 @@ def pl_fc_entails(KB, q): Definite clauses KB example """ definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', + 'C']: definite_clauses_KB.tell(expr(clause)) + # ______________________________________________________________________________ # DPLL-Satisfiable [Figure 7.17] @@ -665,7 +672,7 @@ def unit_clause_assign(clause, model): if model[sym] == positive: return None, None # clause already True elif P: - return None, None # more than 1 unbound variable + return None, None # more than 1 unbound variable else: P, value = sym, positive return P, value @@ -684,6 +691,7 @@ def inspect_literal(literal): else: return literal, True + # ______________________________________________________________________________ # Walk-SAT [Figure 7.18] @@ -714,95 +722,169 @@ def sat_count(sym): count = len([clause for clause in clauses if pl_true(clause, model)]) model[sym] = not model[sym] return count + sym = argmax(prop_symbols(clause), key=sat_count) model[sym] = not model[sym] # If no solution is found within the flip limit, we return failure return None + +# ______________________________________________________________________________ +# Map Coloring Problems + + +def MapColoringSAT(colors, neighbors): + """Make a SAT for the problem of coloring a map with different colors + for any two adjacent regions. Arguments are a list of colors, and a + dict of {region: [neighbor,...]} entries. This dict may also be + specified as a string of the form defined by parse_neighbors.""" + if isinstance(neighbors, str): + neighbors = parse_neighbors(neighbors) + colors = UniversalDict(colors) + clauses = [] + for state in neighbors.keys(): + clause = [expr(state + '_' + c) for c in colors[state]] + clauses.append(clause) + for t in itertools.combinations(clause, 2): + clauses.append([~t[0], ~t[1]]) + visited = set() + adj = set(neighbors[state]) - visited + visited.add(state) + for n_state in adj: + for col in colors[n_state]: + clauses.append([expr('~' + state + '_' + col), expr('~' + n_state + '_' + col)]) + return associate('&', map(lambda c: associate('|', c), clauses)) + + +australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) + +france_sat = MapColoringSAT(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") + +usa_sat = MapColoringSAT(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + + # ______________________________________________________________________________ # Expr functions for WumpusKB and HybridWumpusAgent -def facing_east (time): +def facing_east(time): return Expr('FacingEast', time) -def facing_west (time): + +def facing_west(time): return Expr('FacingWest', time) -def facing_north (time): + +def facing_north(time): return Expr('FacingNorth', time) -def facing_south (time): + +def facing_south(time): return Expr('FacingSouth', time) -def wumpus (x, y): + +def wumpus(x, y): return Expr('W', x, y) + def pit(x, y): return Expr('P', x, y) + def breeze(x, y): return Expr('B', x, y) + def stench(x, y): return Expr('S', x, y) + def wumpus_alive(time): return Expr('WumpusAlive', time) + def have_arrow(time): return Expr('HaveArrow', time) + def percept_stench(time): return Expr('Stench', time) + def percept_breeze(time): return Expr('Breeze', time) + def percept_glitter(time): return Expr('Glitter', time) + def percept_bump(time): return Expr('Bump', time) + def percept_scream(time): return Expr('Scream', time) + def move_forward(time): return Expr('Forward', time) + def shoot(time): return Expr('Shoot', time) + def turn_left(time): return Expr('TurnLeft', time) + def turn_right(time): return Expr('TurnRight', time) + def ok_to_move(x, y, time): return Expr('OK', x, y, time) -def location(x, y, time = None): + +def location(x, y, time=None): if time is None: return Expr('L', x, y) else: return Expr('L', x, y, time) + # Symbols def implies(lhs, rhs): return Expr('==>', lhs, rhs) + def equiv(lhs, rhs): return Expr('<=>', lhs, rhs) + # Helper Function def new_disjunction(sentences): t = sentences[0] - for i in range(1,len(sentences)): + for i in range(1, len(sentences)): t |= sentences[i] return t @@ -812,62 +894,59 @@ def new_disjunction(sentences): class WumpusKB(PropKB): """ - Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. + Create a Knowledge Base that contains the a temporal "Wumpus physics" and temporal rules with time zero. """ - def __init__(self,dimrow): + def __init__(self, dimrow): super().__init__() self.dimrow = dimrow - self.tell( ~wumpus(1, 1) ) - self.tell( ~pit(1, 1) ) + self.tell(~wumpus(1, 1)) + self.tell(~pit(1, 1)) - for y in range(1, dimrow+1): - for x in range(1, dimrow+1): + for y in range(1, dimrow + 1): + for x in range(1, dimrow + 1): pits_in = list() wumpus_in = list() - if x > 1: # West room exists + if x > 1: # West room exists pits_in.append(pit(x - 1, y)) wumpus_in.append(wumpus(x - 1, y)) - if y < dimrow: # North room exists + if y < dimrow: # North room exists pits_in.append(pit(x, y + 1)) wumpus_in.append(wumpus(x, y + 1)) - if x < dimrow: # East room exists + if x < dimrow: # East room exists pits_in.append(pit(x + 1, y)) wumpus_in.append(wumpus(x + 1, y)) - if y > 1: # South room exists + if y > 1: # South room exists pits_in.append(pit(x, y - 1)) wumpus_in.append(wumpus(x, y - 1)) self.tell(equiv(breeze(x, y), new_disjunction(pits_in))) self.tell(equiv(stench(x, y), new_disjunction(wumpus_in))) - - ## Rule that describes existence of at least one Wumpus + # Rule that describes existence of at least one Wumpus wumpus_at_least = list() - for x in range(1, dimrow+1): + for x in range(1, dimrow + 1): for y in range(1, dimrow + 1): wumpus_at_least.append(wumpus(x, y)) self.tell(new_disjunction(wumpus_at_least)) - - ## Rule that describes existence of at most one Wumpus - for i in range(1, dimrow+1): - for j in range(1, dimrow+1): - for u in range(1, dimrow+1): - for v in range(1, dimrow+1): - if i!=u or j!=v: + # Rule that describes existence of at most one Wumpus + for i in range(1, dimrow + 1): + for j in range(1, dimrow + 1): + for u in range(1, dimrow + 1): + for v in range(1, dimrow + 1): + if i != u or j != v: self.tell(~wumpus(i, j) | ~wumpus(u, v)) - - ## Temporal rules at time zero + # Temporal rules at time zero self.tell(location(1, 1, 0)) - for i in range(1, dimrow+1): + for i in range(1, dimrow + 1): for j in range(1, dimrow + 1): self.tell(implies(location(i, j, 0), equiv(percept_breeze(0), breeze(i, j)))) self.tell(implies(location(i, j, 0), equiv(percept_stench(0), stench(i, j)))) @@ -881,7 +960,6 @@ def __init__(self,dimrow): self.tell(~facing_south(0)) self.tell(~facing_west(0)) - def make_action_sentence(self, action, time): actions = [move_forward(time), shoot(time), turn_left(time), turn_right(time)] @@ -895,7 +973,7 @@ def make_percept_sentence(self, percept, time): # Glitter, Bump, Stench, Breeze, Scream flags = [0, 0, 0, 0, 0] - ## Things perceived + # Things perceived if isinstance(percept, Glitter): flags[0] = 1 self.tell(percept_glitter(time)) @@ -912,7 +990,7 @@ def make_percept_sentence(self, percept, time): flags[4] = 1 self.tell(percept_scream(time)) - ## Things not perceived + # Things not perceived for i in range(len(flags)): if flags[i] == 0: if i == 0: @@ -926,15 +1004,14 @@ def make_percept_sentence(self, percept, time): elif i == 4: self.tell(~percept_scream(time)) - def add_temporal_sentences(self, time): if time == 0: return t = time - 1 - ## current location rules - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + # current location rules + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): self.tell(implies(location(i, j, time), equiv(percept_breeze(time), breeze(i, j)))) self.tell(implies(location(i, j, time), equiv(percept_stench(time), stench(i, j)))) @@ -956,15 +1033,15 @@ def add_temporal_sentences(self, time): if j != self.dimrow: s.append(location(i, j + 1, t) & facing_south(t) & move_forward(t)) - ## add sentence about location i,j + # add sentence about location i,j self.tell(new_disjunction(s)) - ## add sentence about safety of location i,j + # add sentence about safety of location i,j self.tell( equiv(ok_to_move(i, j, time), ~pit(i, j) & ~wumpus(i, j) & wumpus_alive(time)) ) - ## Rules about current orientation + # Rules about current orientation a = facing_north(t) & turn_right(t) b = facing_south(t) & turn_left(t) @@ -990,16 +1067,15 @@ def add_temporal_sentences(self, time): s = equiv(facing_south(time), a | b | c) self.tell(s) - ## Rules about last action + # Rules about last action self.tell(equiv(move_forward(t), ~turn_right(t) & ~turn_left(t))) - ##Rule about the arrow + # Rule about the arrow self.tell(equiv(have_arrow(time), have_arrow(t) & ~shoot(t))) - ##Rule about Wumpus (dead or alive) + # Rule about Wumpus (dead or alive) self.tell(equiv(wumpus_alive(time), wumpus_alive(t) & ~percept_scream(time))) - def ask_if_true(self, query): return pl_resolution(self, query) @@ -1007,13 +1083,12 @@ def ask_if_true(self, query): # ______________________________________________________________________________ -class WumpusPosition(): +class WumpusPosition: def __init__(self, x, y, orientation): self.X = x self.Y = y self.orientation = orientation - def get_location(self): return self.X, self.Y @@ -1028,19 +1103,19 @@ def set_orientation(self, orientation): self.orientation = orientation def __eq__(self, other): - if other.get_location() == self.get_location() and \ - other.get_orientation()==self.get_orientation(): + if other.get_location() == self.get_location() and other.get_orientation() == self.get_orientation(): return True else: return False + # ______________________________________________________________________________ class HybridWumpusAgent(Agent): """An agent for the wumpus world that does logical inference. [Figure 7.20]""" - def __init__(self,dimentions): + def __init__(self, dimentions): self.dimrow = dimentions self.kb = WumpusKB(self.dimrow) self.t = 0 @@ -1048,15 +1123,14 @@ def __init__(self,dimentions): self.current_position = WumpusPosition(1, 1, 'UP') super().__init__(self.execute) - def execute(self, percept): self.kb.make_percept_sentence(percept, self.t) self.kb.add_temporal_sentences(self.t) temp = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if self.kb.ask_if_true(location(i, j, self.t)): temp.append(i) temp.append(j) @@ -1071,8 +1145,8 @@ def execute(self, percept): self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT') safe_points = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if self.kb.ask_if_true(ok_to_move(i, j, self.t)): safe_points.append([i, j]) @@ -1080,14 +1154,14 @@ def execute(self, percept): goals = list() goals.append([1, 1]) self.plan.append('Grab') - actions = self.plan_route(self.current_position,goals,safe_points) + actions = self.plan_route(self.current_position, goals, safe_points) self.plan.extend(actions) self.plan.append('Climb') if len(self.plan) == 0: unvisited = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): for k in range(self.t): if self.kb.ask_if_true(location(i, j, k)): unvisited.append([i, j]) @@ -1097,13 +1171,13 @@ def execute(self, percept): if u not in unvisited_and_safe and s == u: unvisited_and_safe.append(u) - temp = self.plan_route(self.current_position,unvisited_and_safe,safe_points) + temp = self.plan_route(self.current_position, unvisited_and_safe, safe_points) self.plan.extend(temp) if len(self.plan) == 0 and self.kb.ask_if_true(have_arrow(self.t)): possible_wumpus = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if not self.kb.ask_if_true(wumpus(i, j)): possible_wumpus.append([i, j]) @@ -1112,8 +1186,8 @@ def execute(self, percept): if len(self.plan) == 0: not_unsafe = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if not self.kb.ask_if_true(ok_to_move(i, j, self.t)): not_unsafe.append([i, j]) temp = self.plan_route(self.current_position, not_unsafe, safe_points) @@ -1133,19 +1207,17 @@ def execute(self, percept): return action - def plan_route(self, current, goals, allowed): problem = PlanRoute(current, goals, allowed, self.dimrow) return astar_search(problem).solution() - def plan_shot(self, current, goals, allowed): shooting_positions = set() for loc in goals: x = loc[0] y = loc[1] - for i in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): if i < x: shooting_positions.add(WumpusPosition(i, y, 'EAST')) if i > x: @@ -1157,7 +1229,7 @@ def plan_shot(self, current, goals, allowed): # Can't have a shooting position from any of the rooms the Wumpus could reside orientations = ['EAST', 'WEST', 'NORTH', 'SOUTH'] - for loc in goals: + for loc in goals: for orientation in orientations: shooting_positions.remove(WumpusPosition(loc[0], loc[1], orientation)) @@ -1186,7 +1258,7 @@ def translate_to_SAT(init, transition, goal, time): # Symbol claiming state s at time t state_counter = itertools.count() for s in states: - for t in range(time+1): + for t in range(time + 1): state_sym[s, t] = Expr("State_{}".format(next(state_counter))) # Add initial state axiom @@ -1206,11 +1278,11 @@ def translate_to_SAT(init, transition, goal, time): "Transition_{}".format(next(transition_counter))) # Change the state from s to s_ - clauses.append(action_sym[s, action, t] |'==>'| state_sym[s, t]) - clauses.append(action_sym[s, action, t] |'==>'| state_sym[s_, t + 1]) + clauses.append(action_sym[s, action, t] | '==>' | state_sym[s, t]) + clauses.append(action_sym[s, action, t] | '==>' | state_sym[s_, t + 1]) # Allow only one state at any time - for t in range(time+1): + for t in range(time + 1): # must be a state at any time clauses.append(associate('|', [state_sym[s, t] for s in states])) @@ -1363,6 +1435,7 @@ def standardize_variables(sentence, dic=None): standardize_variables.counter = itertools.count() + # ______________________________________________________________________________ @@ -1404,6 +1477,7 @@ def fol_fc_ask(KB, alpha): """A simple forward-chaining algorithm. [Figure 9.3]""" # TODO: Improve efficiency kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)}) + def enum_subst(p): query_vars = list({v for clause in p for v in variables(clause)}) for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)): @@ -1466,8 +1540,8 @@ def fol_bc_and(KB, goals, theta): P11, P12, P21, P22, P31, B11, B21 = expr('P11, P12, P21, P22, P31, B11, B21') wumpus_kb.tell(~P11) -wumpus_kb.tell(B11 | '<=>' | ((P12 | P21))) -wumpus_kb.tell(B21 | '<=>' | ((P11 | P22 | P31))) +wumpus_kb.tell(B11 | '<=>' | (P12 | P21)) +wumpus_kb.tell(B21 | '<=>' | (P11 | P22 | P31)) wumpus_kb.tell(~B11) wumpus_kb.tell(B21) @@ -1497,6 +1571,7 @@ def fol_bc_and(KB, goals, theta): 'Enemy(Nono, America)' ])) + # ______________________________________________________________________________ # Example application (not in the book). @@ -1527,7 +1602,7 @@ def diff(y, x): elif op == '/': return (v * diff(u, x) - u * diff(v, x)) / (v * v) elif op == '**' and isnumber(x.op): - return (v * u ** (v - 1) * diff(u, x)) + return v * u ** (v - 1) * diff(u, x) elif op == '**': return (v * u ** (v - 1) * diff(u, x) + u ** v * Expr('log')(u) * diff(v, x)) diff --git a/tests/test_csp.py b/tests/test_csp.py index 77b35c796..b7bec9a87 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -10,16 +10,16 @@ def test_csp_assign(): var = 10 val = 5 assignment = {} - australia.assign(var, val, assignment) + australia_csp.assign(var, val, assignment) - assert australia.nassigns == 1 + assert australia_csp.nassigns == 1 assert assignment[var] == val def test_csp_unassign(): var = 10 assignment = {var: 5} - australia.unassign(var, assignment) + australia_csp.unassign(var, assignment) assert var not in assignment @@ -330,22 +330,22 @@ def test_forward_checking(): def test_backtracking_search(): - assert backtracking_search(australia) - assert backtracking_search(australia, select_unassigned_variable=mrv) - assert backtracking_search(australia, order_domain_values=lcv) - assert backtracking_search(australia, select_unassigned_variable=mrv, + assert backtracking_search(australia_csp) + assert backtracking_search(australia_csp, select_unassigned_variable=mrv) + assert backtracking_search(australia_csp, order_domain_values=lcv) + assert backtracking_search(australia_csp, select_unassigned_variable=mrv, order_domain_values=lcv) - assert backtracking_search(australia, inference=forward_checking) - assert backtracking_search(australia, inference=mac) - assert backtracking_search(usa, select_unassigned_variable=mrv, + assert backtracking_search(australia_csp, inference=forward_checking) + assert backtracking_search(australia_csp, inference=mac) + assert backtracking_search(usa_csp, select_unassigned_variable=mrv, order_domain_values=lcv, inference=mac) def test_min_conflicts(): - assert min_conflicts(australia) - assert min_conflicts(france) + assert min_conflicts(australia_csp) + assert min_conflicts(france_csp) - tests = [(usa, None)] * 3 + tests = [(usa_csp, None)] * 3 assert failure_test(min_conflicts, tests) >= 1 / 3 australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') @@ -418,7 +418,7 @@ def test_parse_neighbours(): def test_topological_sort(): root = 'NT' - Sort, Parents = topological_sort(australia, root) + Sort, Parents = topological_sort(australia_csp, root) assert Sort == ['NT', 'SA', 'Q', 'NSW', 'V', 'WA'] assert Parents['NT'] == None diff --git a/tests/test_logic.py b/tests/test_logic.py index 378f1f0fc..2d4468d0d 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -1,10 +1,12 @@ import pytest + from logic import * -from utils import expr_handle_infix_ops, count, Symbol +from utils import expr_handle_infix_ops, count definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: - definite_clauses_KB.tell(expr(clause)) +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', + 'C']: + definite_clauses_KB.tell(expr(clause)) def test_is_symbol(): @@ -47,7 +49,7 @@ def test_extend(): def test_subst(): - assert subst({x: 42, y:0}, F(x) + y) == (F(42) + 0) + assert subst({x: 42, y: 0}, F(x) + y) == (F(42) + 0) def test_PropKB(): @@ -55,7 +57,7 @@ def test_PropKB(): assert count(kb.ask(expr) for expr in [A, C, D, E, Q]) is 0 kb.tell(A & E) assert kb.ask(A) == kb.ask(E) == {} - kb.tell(E |'==>'| C) + kb.tell(E | '==>' | C) assert kb.ask(C) == {} kb.retract(E) assert kb.ask(E) is False @@ -94,7 +96,8 @@ def test_is_definite_clause(): def test_parse_definite_clause(): assert parse_definite_clause(expr('A & B & C & D ==> E')) == ([A, B, C, D], E) assert parse_definite_clause(expr('Farmer(Mac)')) == ([], expr('Farmer(Mac)')) - assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ([expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) + assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ( + [expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) def test_pl_true(): @@ -131,28 +134,28 @@ def test_dpll(): assert (dpll_satisfiable(A & ~B & C & (A | ~D) & (~E | ~D) & (C | ~D) & (~A | ~F) & (E | ~F) & (~D | ~F) & (B | ~C | D) & (A | ~E | F) & (~A | E | D)) == {B: False, C: True, A: True, F: False, D: True, E: False}) - assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} - assert dpll_satisfiable((A | (B & C)) |'<=>'| ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} - assert dpll_satisfiable(A |'<=>'| B) == {A: True, B: True} + assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} + assert dpll_satisfiable((A | (B & C)) | '<=>' | ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} + assert dpll_satisfiable(A | '<=>' | B) == {A: True, B: True} assert dpll_satisfiable(A & ~B) == {A: True, B: False} assert dpll_satisfiable(P & ~P) is False def test_find_pure_symbol(): - assert find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A]) == (A, True) - assert find_pure_symbol([A, B, C], [~A|~B,~B|~C,C|A]) == (B, False) - assert find_pure_symbol([A, B, C], [~A|B,~B|~C,C|A]) == (None, None) + assert find_pure_symbol([A, B, C], [A | ~B, ~B | ~C, C | A]) == (A, True) + assert find_pure_symbol([A, B, C], [~A | ~B, ~B | ~C, C | A]) == (B, False) + assert find_pure_symbol([A, B, C], [~A | B, ~B | ~C, C | A]) == (None, None) def test_unit_clause_assign(): - assert unit_clause_assign(A|B|C, {A:True}) == (None, None) - assert unit_clause_assign(B|C, {A:True}) == (None, None) - assert unit_clause_assign(B|~A, {A:True}) == (B, True) + assert unit_clause_assign(A | B | C, {A: True}) == (None, None) + assert unit_clause_assign(B | C, {A: True}) == (None, None) + assert unit_clause_assign(B | ~A, {A: True}) == (B, True) def test_find_unit_clause(): - assert find_unit_clause([A|B|C, B|~C, ~A|~B], {A:True}) == (B, False) - + assert find_unit_clause([A | B | C, B | ~C, ~A | ~B], {A: True}) == (B, False) + def test_unify(): assert unify(x, x, {}) == {} @@ -175,9 +178,9 @@ def test_tt_entails(): assert tt_entails(P & Q, Q) assert not tt_entails(P | Q, Q) assert tt_entails(A & (B | C) & E & F & ~(P | Q), A & E & F & ~P & ~Q) - assert not tt_entails(P |'<=>'| Q, Q) - assert tt_entails((P |'==>'| Q) & P, Q) - assert not tt_entails((P |'<=>'| Q) & ~P, Q) + assert not tt_entails(P | '<=>' | Q, Q) + assert tt_entails((P | '==>' | Q) & P, Q) + assert not tt_entails((P | '<=>' | Q) & ~P, Q) def test_prop_symbols(): @@ -231,12 +234,13 @@ def test_move_not_inwards(): def test_distribute_and_over_or(): - def test_entailment(s, has_and = False): + def test_entailment(s, has_and=False): result = distribute_and_over_or(s) if has_and: assert result.op == '&' assert tt_entails(s, result) assert tt_entails(result, s) + test_entailment((A & B) | C, True) test_entailment((A | B) & C, True) test_entailment((A | B) | C, False) @@ -253,7 +257,8 @@ def test_to_cnf(): assert repr(to_cnf("a | (b & c) | d")) == '((b | a | d) & (c | a | d))' assert repr(to_cnf("A & (B | (D & E))")) == '(A & (D | B) & (E | B))' assert repr(to_cnf("A | (B | (C | (D & E)))")) == '((D | A | B | C) & (E | A | B | C))' - assert repr(to_cnf('(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' + assert repr(to_cnf( + '(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' def test_pl_resolution(): @@ -281,6 +286,7 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) + assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]' @@ -295,6 +301,7 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) + assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' assert repr(test_ask('Enemy(x, America)', crime_kb)) == '[{x: Nono}]' assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' @@ -316,6 +323,7 @@ def check_SAT(clauses, single_solution={}): if single_solution: # Cross check the solution if only one exists assert all(pl_true(x, single_solution) for x in clauses) assert soln == single_solution + # Test WalkSat for problems with solution check_SAT([A & B, A & C]) check_SAT([A | B, P & Q, P & B]) From 6641c2c861728f3d43d3931ef201c6f7093cbc96 Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Thu, 4 Jul 2019 20:16:42 +0200 Subject: [PATCH 16/21] fixed build error --- agents_4e.py | 81 +++++++++++++++---------- tests/test_agents_4e.py | 129 +++++++++++++++++++++------------------- 2 files changed, 116 insertions(+), 94 deletions(-) diff --git a/agents_4e.py b/agents_4e.py index debd9441e..b357f5251 100644 --- a/agents_4e.py +++ b/agents_4e.py @@ -113,9 +113,11 @@ def new_program(percept): action = old_program(percept) print('{} perceives {} and does {}'.format(agent, percept, action)) return action + agent.program = new_program return agent + # ______________________________________________________________________________ @@ -130,6 +132,7 @@ def program(percept): percepts.append(percept) action = table.get(tuple(percepts)) return action + return program @@ -146,26 +149,31 @@ def RandomAgentProgram(actions): """ return lambda percept: random.choice(actions) + # ______________________________________________________________________________ def SimpleReflexAgentProgram(rules, interpret_input): """This agent takes action based solely on the percept. [Figure 2.10]""" + def program(percept): state = interpret_input(percept) rule = rule_match(state, rules) action = rule.action return action + return program def ModelBasedReflexAgentProgram(rules, update_state, trainsition_model, sensor_model): """This agent takes action based on the percept and state. [Figure 2.12]""" + def program(percept): program.state = update_state(program.state, program.action, percept, trainsition_model, sensor_model) rule = rule_match(program.state, rules) action = rule.action return action + program.state = program.action = None return program @@ -176,6 +184,7 @@ def rule_match(state, rules): if rule.matches(state): return rule + # ______________________________________________________________________________ @@ -219,6 +228,7 @@ def ReflexVacuumAgent(): >>> environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} True """ + def program(percept): location, status = percept if status == 'Dirty': @@ -227,6 +237,7 @@ def program(percept): return 'Right' elif location == loc_B: return 'Left' + return Agent(program) @@ -253,8 +264,10 @@ def program(percept): return 'Right' elif location == loc_B: return 'Left' + return Agent(program) + # ______________________________________________________________________________ @@ -392,22 +405,22 @@ def __add__(self, heading): True """ if self.direction == self.R: - return{ + return { self.R: Direction(self.D), self.L: Direction(self.U), }.get(heading, None) elif self.direction == self.L: - return{ + return { self.R: Direction(self.U), self.L: Direction(self.D), }.get(heading, None) elif self.direction == self.U: - return{ + return { self.R: Direction(self.R), self.L: Direction(self.L), }.get(heading, None) elif self.direction == self.D: - return{ + return { self.R: Direction(self.L), self.L: Direction(self.R), }.get(heading, None) @@ -425,13 +438,13 @@ def move_forward(self, from_location): """ x, y = from_location if self.direction == self.R: - return (x + 1, y) + return x + 1, y elif self.direction == self.L: - return (x - 1, y) + return x - 1, y elif self.direction == self.U: - return (x, y - 1) + return x, y - 1 elif self.direction == self.D: - return (x, y + 1) + return x, y + 1 class XYEnvironment(Environment): @@ -462,7 +475,7 @@ def things_near(self, location, radius=None): radius2 = radius * radius return [(thing, radius2 - distance_squared(location, thing.location)) for thing in self.things if distance_squared( - location, thing.location) <= radius2] + location, thing.location) <= radius2] def percept(self, agent): """By default, agent perceives things within a default radius.""" @@ -476,17 +489,17 @@ def execute_action(self, agent, action): agent.direction += Direction.L elif action == 'Forward': agent.bump = self.move_to(agent, agent.direction.move_forward(agent.location)) -# elif action == 'Grab': -# things = [thing for thing in self.list_things_at(agent.location) -# if agent.can_grab(thing)] -# if things: -# agent.holding.append(things[0]) + # elif action == 'Grab': + # things = [thing for thing in self.list_things_at(agent.location) + # if agent.can_grab(thing)] + # if things: + # agent.holding.append(things[0]) elif action == 'Release': if agent.holding: agent.holding.pop() def default_location(self, thing): - return (random.choice(self.width), random.choice(self.height)) + return random.choice(self.width), random.choice(self.height) def move_to(self, thing, destination): """Move a thing to a new location. Returns True on success or False if there is an Obstacle. @@ -505,7 +518,7 @@ def move_to(self, thing, destination): def add_thing(self, thing, location=(1, 1), exclude_duplicate_class_items=False): """Add things to the world. If (exclude_duplicate_class_items) then the item won't be added if the location has at least one item of the same class.""" - if (self.is_inbounds(location)): + if self.is_inbounds(location): if (exclude_duplicate_class_items and any(isinstance(t, thing.__class__) for t in self.list_things_at(location))): return @@ -514,14 +527,14 @@ def add_thing(self, thing, location=(1, 1), exclude_duplicate_class_items=False) def is_inbounds(self, location): """Checks to make sure that the location is inbounds (within walls if we have walls)""" x, y = location - return not (x < self.x_start or x >= self.x_end or y < self.y_start or y >= self.y_end) + return not (x < self.x_start or x > self.x_end or y < self.y_start or y > self.y_end) def random_location_inbounds(self, exclude=None): """Returns a random location that is inbounds (within walls if we have walls)""" location = (random.randint(self.x_start, self.x_end), random.randint(self.y_start, self.y_end)) if exclude is not None: - while(location == exclude): + while location == exclude: location = (random.randint(self.x_start, self.x_end), random.randint(self.y_start, self.y_end)) return location @@ -543,7 +556,7 @@ def add_walls(self): for x in range(self.width): self.add_thing(Wall(), (x, 0)) self.add_thing(Wall(), (x, self.height - 1)) - for y in range(1, self.height-1): + for y in range(1, self.height - 1): self.add_thing(Wall(), (0, y)) self.add_thing(Wall(), (self.width - 1, y)) @@ -574,6 +587,7 @@ class Obstacle(Thing): class Wall(Obstacle): pass + # ______________________________________________________________________________ @@ -682,6 +696,7 @@ def __init__(self, coordinates): super().__init__() self.coordinates = coordinates + # ______________________________________________________________________________ # Vacuum environment @@ -691,7 +706,6 @@ class Dirt(Thing): class VacuumEnvironment(XYEnvironment): - """The environment of [Ex. 2.12]. Agent perceives dirty or clean, and bump (into obstacle) or not; 2D discrete world of unknown size; performance measure is 100 for each dirt cleaned, and -1 for @@ -710,7 +724,7 @@ def percept(self, agent): Unlike the TrivialVacuumEnvironment, location is NOT perceived.""" status = ('Dirty' if self.some_things_at( agent.location, Dirt) else 'Clean') - bump = ('Bump' if agent.bump else'None') + bump = ('Bump' if agent.bump else 'None') return (status, bump) def execute_action(self, agent, action): @@ -729,7 +743,6 @@ def execute_action(self, agent, action): class TrivialVacuumEnvironment(Environment): - """This environment has two locations, A and B. Each can be Dirty or Clean. The agent perceives its location and the location's status. This serves as an example of how to implement a simple @@ -766,6 +779,7 @@ def default_location(self, thing): """Agents start in either location at random.""" return random.choice([loc_A, loc_B]) + # ______________________________________________________________________________ # The Wumpus World @@ -775,6 +789,7 @@ class Gold(Thing): def __eq__(self, rhs): """All Gold are equal""" return rhs.__class__ == Gold + pass @@ -824,6 +839,7 @@ def can_grab(self, thing): class WumpusEnvironment(XYEnvironment): pit_probability = 0.2 # Probability to spawn a pit in a location. (From Chapter 7.2) + # Room should be 4x4 grid of rooms. The extra 2 for walls def __init__(self, agent_program, width=6, height=6): @@ -901,12 +917,9 @@ def percept(self, agent): """Return things in adjacent (not diagonal) cells of the agent. Result format: [Left, Right, Up, Down, Center / Current location]""" x, y = agent.location - result = [] - result.append(self.percepts_from(agent, (x - 1, y))) - result.append(self.percepts_from(agent, (x + 1, y))) - result.append(self.percepts_from(agent, (x, y - 1))) - result.append(self.percepts_from(agent, (x, y + 1))) - result.append(self.percepts_from(agent, (x, y))) + result = [self.percepts_from(agent, (x - 1, y)), self.percepts_from(agent, (x + 1, y)), + self.percepts_from(agent, (x, y - 1)), self.percepts_from(agent, (x, y + 1)), + self.percepts_from(agent, (x, y))] """The wumpus gives out a loud scream once it's killed.""" wumpus = [thing for thing in self.things if isinstance(thing, Wumpus)] @@ -949,7 +962,7 @@ def execute_action(self, agent, action): """The arrow travels straight down the path the agent is facing""" if agent.has_arrow: arrow_travel = agent.direction.move_forward(agent.location) - while(self.is_inbounds(arrow_travel)): + while self.is_inbounds(arrow_travel): wumpus = [thing for thing in self.list_things_at(arrow_travel) if isinstance(thing, Wumpus)] if len(wumpus): @@ -979,12 +992,13 @@ def is_done(self): print("Death by {} [-1000].".format(explorer[0].killed_by)) else: print("Explorer climbed out {}." - .format( - "with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) + .format( + "with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) return True - # TODO: Arrow needs to be implemented + + # ______________________________________________________________________________ @@ -1016,13 +1030,16 @@ def test_agent(AgentFactory, steps, envs): >>> result == 5 True """ + def score(env): agent = AgentFactory() env.add_thing(agent) env.run(steps) return agent.performance + return mean(map(score, envs)) + # _________________________________________________________________________ diff --git a/tests/test_agents_4e.py b/tests/test_agents_4e.py index ca082887e..3ebc258cb 100644 --- a/tests/test_agents_4e.py +++ b/tests/test_agents_4e.py @@ -1,12 +1,12 @@ import random -from agents_4e import Direction + from agents_4e import Agent -from agents_4e import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ - RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ - SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, rule_match +from agents_4e import Direction +from agents_4e import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents, \ + RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ + SimpleReflexAgentProgram, ModelBasedReflexAgentProgram from agents_4e import Wall, Gold, Explorer, Thing, Bump, Glitter, WumpusEnvironment, Pit, \ - VacuumEnvironment, Dirt - + VacuumEnvironment, Dirt random.seed("aima-python") @@ -58,12 +58,12 @@ def test_add(): assert l2.direction == Direction.D -def test_RandomAgentProgram() : - #create a list of all the actions a vacuum cleaner can perform +def test_RandomAgentProgram(): + # create a list of all the actions a vacuum cleaner can perform list = ['Right', 'Left', 'Suck', 'NoOp'] # create a program and then an object of the RandomAgentProgram program = RandomAgentProgram(list) - + agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -72,10 +72,10 @@ def test_RandomAgentProgram() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean' , (0, 0): 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_RandomVacuumAgent() : +def test_RandomVacuumAgent(): # create an object of the RandomVacuumAgent agent = RandomVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -85,7 +85,7 @@ def test_RandomVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} def test_TableDrivenAgent(): @@ -109,22 +109,21 @@ def test_TableDrivenAgent(): # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() # initializing some environment status - environment.status = {loc_A:'Dirty', loc_B:'Dirty'} + environment.status = {loc_A: 'Dirty', loc_B: 'Dirty'} # add agent to the environment - environment.add_thing(agent) - + environment.add_thing(agent, location=(1, 0)) # run the environment by single step everytime to check how environment evolves using TableDrivenAgentProgram - environment.run(steps = 1) - assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} + environment.run(steps=1) + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} - environment.run(steps = 1) - assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} + environment.run(steps=1) + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} - environment.run(steps = 1) - assert environment.status == {(1,0): 'Clean', (0,0): 'Clean'} + environment.run(steps=1) + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_ReflexVacuumAgent() : +def test_ReflexVacuumAgent(): # create an object of the ReflexVacuumAgent agent = ReflexVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -134,31 +133,31 @@ def test_ReflexVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} def test_SimpleReflexAgentProgram(): class Rule: - + def __init__(self, state, action): self.__state = state self.action = action - + def matches(self, state): return self.__state == state - + loc_A = (0, 0) loc_B = (1, 0) - + # create rules for a two state Vacuum Environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] - + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + def interpret_input(state): return state - + # create a program and then an object of the SimpleReflexAgentProgram - program = SimpleReflexAgentProgram(rules, interpret_input) + program = SimpleReflexAgentProgram(rules, interpret_input) agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -167,7 +166,7 @@ def interpret_input(state): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} def test_ModelBasedReflexAgentProgram(): @@ -185,7 +184,7 @@ def matches(self, state): # create rules for a two-state vacuum environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] def update_state(state, action, percept, transition_model, sensor_model): return percept @@ -203,7 +202,7 @@ def update_state(state, action, percept, transition_model, sensor_model): assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_ModelBasedVacuumAgent() : +def test_ModelBasedVacuumAgent(): # create an object of the ModelBasedVacuumAgent agent = ModelBasedVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -213,10 +212,10 @@ def test_ModelBasedVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_TableDrivenVacuumAgent() : +def test_TableDrivenVacuumAgent(): # create an object of the TableDrivenVacuumAgent agent = TableDrivenVacuumAgent() # create an object of the TrivialVacuumEnvironment @@ -226,10 +225,10 @@ def test_TableDrivenVacuumAgent() : # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0):'Clean', (0, 0):'Clean'} + assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_compare_agents() : +def test_compare_agents(): environment = TrivialVacuumEnvironment agents = [ModelBasedVacuumAgent, ReflexVacuumAgent] @@ -263,24 +262,26 @@ def test_TableDrivenAgentProgram(): def test_Agent(): def constant_prog(percept): return percept + agent = Agent(constant_prog) result = agent.program(5) assert result == 5 + def test_VacuumEnvironment(): # Initialize Vacuum Environment - v = VacuumEnvironment(6,6) - #Get an agent + v = VacuumEnvironment(6, 6) + # Get an agent agent = ModelBasedVacuumAgent() agent.direction = Direction(Direction.R) v.add_thing(agent) - v.add_thing(Dirt(), location=(2,1)) + v.add_thing(Dirt(), location=(2, 1)) # Check if things are added properly assert len([x for x in v.things if isinstance(x, Wall)]) == 20 assert len([x for x in v.things if isinstance(x, Dirt)]) == 1 - #Let the action begin! + # Let the action begin! assert v.percept(agent) == ("Clean", "None") v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "None") @@ -288,65 +289,69 @@ def test_VacuumEnvironment(): v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "Bump") v.execute_action(agent, "Suck") - assert v.percept(agent) == ("Clean", "None") + assert v.percept(agent) == ("Clean", "None") old_performance = agent.performance v.execute_action(agent, "NoOp") assert old_performance == agent.performance + def test_WumpusEnvironment(): def constant_prog(percept): return percept + # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) - #Check if things are added properly + # Check if things are added properly assert len([x for x in w.things if isinstance(x, Wall)]) == 20 assert any(map(lambda x: isinstance(x, Gold), w.things)) assert any(map(lambda x: isinstance(x, Explorer), w.things)) - assert not any(map(lambda x: not isinstance(x,Thing), w.things)) + assert not any(map(lambda x: not isinstance(x, Thing), w.things)) - #Check that gold and wumpus are not present on (1,1) - assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x,WumpusEnvironment), - w.list_things_at((1, 1)))) + # Check that gold and wumpus are not present on (1,1) + assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x, WumpusEnvironment), + w.list_things_at((1, 1)))) - #Check if w.get_world() segments objects correctly + # Check if w.get_world() segments objects correctly assert len(w.get_world()) == 6 for row in w.get_world(): assert len(row) == 6 - #Start the game! + # Start the game! agent = [x for x in w.things if isinstance(x, Explorer)][0] gold = [x for x in w.things if isinstance(x, Gold)][0] pit = [x for x in w.things if isinstance(x, Pit)][0] - assert w.is_done()==False + assert not w.is_done() - #Check Walls + # Check Walls agent.location = (1, 2) percepts = w.percept(agent) assert len(percepts) == 5 - assert any(map(lambda x: isinstance(x,Bump), percepts[0])) + assert any(map(lambda x: isinstance(x, Bump), percepts[0])) - #Check Gold + # Check Gold agent.location = gold.location percepts = w.percept(agent) - assert any(map(lambda x: isinstance(x,Glitter), percepts[4])) - agent.location = (gold.location[0], gold.location[1]+1) + assert any(map(lambda x: isinstance(x, Glitter), percepts[4])) + agent.location = (gold.location[0], gold.location[1] + 1) percepts = w.percept(agent) - assert not any(map(lambda x: isinstance(x,Glitter), percepts[4])) + assert not any(map(lambda x: isinstance(x, Glitter), percepts[4])) - #Check agent death + # Check agent death agent.location = pit.location - assert w.in_danger(agent) == True - assert agent.alive == False + assert w.in_danger(agent) + assert not agent.alive assert agent.killed_by == Pit.__name__ assert agent.performance == -1000 - assert w.is_done()==True + assert w.is_done() + def test_WumpusEnvironmentActions(): def constant_prog(percept): return percept + # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) @@ -371,4 +376,4 @@ def constant_prog(percept): w.execute_action(agent, 'Climb') assert not any(map(lambda x: isinstance(x, Explorer), w.things)) - assert w.is_done()==True \ No newline at end of file + assert w.is_done() From 9399dfc79458bc076ed193e0b4dfaef86ecc4fa7 Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Mon, 29 Jul 2019 12:53:32 +0200 Subject: [PATCH 17/21] Revert "added map coloring SAT problem" This reverts commit 93af259e4811ddd775429f8a334111b9dd9e268c. --- csp.py | 73 ++++++-------- logic.py | 233 +++++++++++++++----------------------------- tests/test_csp.py | 28 +++--- tests/test_logic.py | 52 +++++----- 4 files changed, 144 insertions(+), 242 deletions(-) diff --git a/csp.py b/csp.py index 4c1203f4a..ee59d4a6b 100644 --- a/csp.py +++ b/csp.py @@ -74,12 +74,10 @@ def unassign(self, var, assignment): def nconflicts(self, var, val, assignment): """Return the number of conflicts var=val has with other variables.""" - # Subclasses may implement this more efficiently def conflict(var2): return (var2 in assignment and not self.constraints(var, val, var2, assignment[var2])) - return count(conflict(v) for v in self.neighbors[var]) def display(self, assignment): @@ -155,7 +153,6 @@ def conflicted_vars(self, current): return [var for var in self.variables if self.nconflicts(var, current[var], current) > 0] - # ______________________________________________________________________________ # Constraint Propagation with AC-3 @@ -186,7 +183,6 @@ def revise(csp, Xi, Xj, removals): revised = True return revised - # ______________________________________________________________________________ # CSP Backtracking Search @@ -212,7 +208,6 @@ def num_legal_values(csp, var, assignment): return count(csp.nconflicts(var, val, assignment) == 0 for val in csp.domains[var]) - # Value ordering @@ -226,7 +221,6 @@ def lcv(var, assignment, csp): return sorted(csp.choices(var), key=lambda val: csp.nconflicts(var, val, assignment)) - # Inference @@ -251,7 +245,6 @@ def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) - # The search, proper @@ -281,7 +274,6 @@ def backtrack(assignment): assert result is None or csp.goal_test(result) return result - # ______________________________________________________________________________ # Min-conflicts hillclimbing search for CSPs @@ -310,7 +302,6 @@ def min_conflicts_value(csp, var, current): return argmin_random_tie(csp.domains[var], key=lambda val: csp.nconflicts(var, val, current)) - # ______________________________________________________________________________ @@ -365,7 +356,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents): visited[node] = True for n in neighbors[node]: - if (not visited[n]): + if(not visited[n]): build_topological(n, node, neighbors, visited, stack, parents) parents[node] = parent @@ -375,9 +366,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents): def make_arc_consistent(Xj, Xk, csp): """Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints, by removing the possible values of Xj that cause inconsistencies.""" - # csp.curr_domains[Xj] = [] + #csp.curr_domains[Xj] = [] for val1 in csp.domains[Xj]: - keep = False # Keep or remove val1 + keep = False # Keep or remove val1 for val2 in csp.domains[Xk]: if csp.constraints(Xj, val1, Xk, val2): # Found a consistent assignment for val1, keep it @@ -402,9 +393,8 @@ def assign_value(Xj, Xk, csp, assignment): # No consistent assignment available return None - # ______________________________________________________________________________ -# Map Coloring Problems +# Map-Coloring Problems class UniversalDict: @@ -456,27 +446,27 @@ def parse_neighbors(neighbors, variables=None): return dic -australia_csp = MapColoringCSP(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) - -usa_csp = MapColoringCSP(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - -france_csp = MapColoringCSP(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") - +australia = MapColoringCSP(list('RGB'), + 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') + +usa = MapColoringCSP(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + +france = MapColoringCSP(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") # ______________________________________________________________________________ # n-Queens Problem @@ -513,16 +503,16 @@ def __init__(self, n): CSP.__init__(self, list(range(n)), UniversalDict(list(range(n))), UniversalDict(list(range(n))), queen_constraint) - self.rows = [0] * n - self.ups = [0] * (2 * n - 1) - self.downs = [0] * (2 * n - 1) + self.rows = [0]*n + self.ups = [0]*(2*n - 1) + self.downs = [0]*(2*n - 1) def nconflicts(self, var, val, assignment): """The number of conflicts, as recorded with each assignment. Count conflicts in row and in up, down diagonals. If there is a queen there, it can't conflict with itself, so subtract 3.""" n = len(self.variables) - c = self.rows[val] + self.downs[var + val] + self.ups[var - val + n - 1] + c = self.rows[val] + self.downs[var+val] + self.ups[var-val+n-1] if assignment.get(var, None) == val: c -= 3 return c @@ -570,7 +560,6 @@ def display(self, assignment): print(str(self.nconflicts(var, val, assignment)) + ch, end=' ') print() - # ______________________________________________________________________________ # Sudoku @@ -657,12 +646,9 @@ def show_cell(cell): return str(assignment.get(cell, '.')) def abut(lines1, lines2): return list( map(' | '.join, list(zip(lines1, lines2)))) - print('\n------+-------+------\n'.join( '\n'.join(reduce( abut, map(show_box, brow))) for brow in self.bgrid)) - - # ______________________________________________________________________________ # The Zebra Puzzle @@ -730,7 +716,6 @@ def zebra_constraint(A, a, B, b, recurse=0): (A in Smokes and B in Smokes)): return not same raise Exception('error') - return CSP(variables, domains, neighbors, zebra_constraint) diff --git a/logic.py b/logic.py index 5ef29212f..6aacc4f95 100644 --- a/logic.py +++ b/logic.py @@ -30,7 +30,7 @@ unify Do unification of two FOL sentences diff, simp Symbolic differentiation and simplification """ -from csp import parse_neighbors, UniversalDict + from utils import ( removeall, unique, first, argmax, probability, isnumber, issequence, Expr, expr, subexpressions @@ -42,11 +42,11 @@ import random from collections import defaultdict - # ______________________________________________________________________________ class KB: + """A knowledge base to which you can tell and ask sentences. To create a KB, first subclass this class and implement tell, ask_generator, and retract. Why ask_generator instead of ask? @@ -106,7 +106,6 @@ def retract(self, sentence): if c in self.clauses: self.clauses.remove(c) - # ______________________________________________________________________________ @@ -320,7 +319,6 @@ def pl_true(exp, model={}): else: raise ValueError("illegal operator in logic expression" + str(exp)) - # ______________________________________________________________________________ # Convert to Conjunctive Normal Form (CNF) @@ -370,7 +368,6 @@ def move_not_inwards(s): if s.op == '~': def NOT(b): return move_not_inwards(~b) - a = s.args[0] if a.op == '~': return move_not_inwards(a.args[0]) # ~~A ==> A @@ -448,7 +445,6 @@ def collect(subargs): collect(arg.args) else: result.append(arg) - collect(args) return result @@ -472,7 +468,6 @@ def disjuncts(s): """ return dissociate('|', [s]) - # ______________________________________________________________________________ @@ -486,7 +481,7 @@ def pl_resolution(KB, alpha): while True: n = len(clauses) pairs = [(clauses[i], clauses[j]) - for i in range(n) for j in range(i + 1, n)] + for i in range(n) for j in range(i+1, n)] for (ci, cj) in pairs: resolvents = pl_resolve(ci, cj) if False in resolvents: @@ -510,7 +505,6 @@ def pl_resolve(ci, cj): clauses.append(associate('|', dnew)) return clauses - # ______________________________________________________________________________ @@ -566,6 +560,7 @@ def pl_fc_entails(KB, q): """ wumpus_world_inference = expr("(B11 <=> (P12 | P21)) & ~B11") + """ [Figure 7.16] Propositional Logic Forward Chaining example """ @@ -577,11 +572,9 @@ def pl_fc_entails(KB, q): Definite clauses KB example """ definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', - 'C']: +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: definite_clauses_KB.tell(expr(clause)) - # ______________________________________________________________________________ # DPLL-Satisfiable [Figure 7.17] @@ -672,7 +665,7 @@ def unit_clause_assign(clause, model): if model[sym] == positive: return None, None # clause already True elif P: - return None, None # more than 1 unbound variable + return None, None # more than 1 unbound variable else: P, value = sym, positive return P, value @@ -691,7 +684,6 @@ def inspect_literal(literal): else: return literal, True - # ______________________________________________________________________________ # Walk-SAT [Figure 7.18] @@ -722,169 +714,95 @@ def sat_count(sym): count = len([clause for clause in clauses if pl_true(clause, model)]) model[sym] = not model[sym] return count - sym = argmax(prop_symbols(clause), key=sat_count) model[sym] = not model[sym] # If no solution is found within the flip limit, we return failure return None - -# ______________________________________________________________________________ -# Map Coloring Problems - - -def MapColoringSAT(colors, neighbors): - """Make a SAT for the problem of coloring a map with different colors - for any two adjacent regions. Arguments are a list of colors, and a - dict of {region: [neighbor,...]} entries. This dict may also be - specified as a string of the form defined by parse_neighbors.""" - if isinstance(neighbors, str): - neighbors = parse_neighbors(neighbors) - colors = UniversalDict(colors) - clauses = [] - for state in neighbors.keys(): - clause = [expr(state + '_' + c) for c in colors[state]] - clauses.append(clause) - for t in itertools.combinations(clause, 2): - clauses.append([~t[0], ~t[1]]) - visited = set() - adj = set(neighbors[state]) - visited - visited.add(state) - for n_state in adj: - for col in colors[n_state]: - clauses.append([expr('~' + state + '_' + col), expr('~' + n_state + '_' + col)]) - return associate('&', map(lambda c: associate('|', c), clauses)) - - -australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) - -france_sat = MapColoringSAT(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") - -usa_sat = MapColoringSAT(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - - # ______________________________________________________________________________ # Expr functions for WumpusKB and HybridWumpusAgent -def facing_east(time): +def facing_east (time): return Expr('FacingEast', time) - -def facing_west(time): +def facing_west (time): return Expr('FacingWest', time) - -def facing_north(time): +def facing_north (time): return Expr('FacingNorth', time) - -def facing_south(time): +def facing_south (time): return Expr('FacingSouth', time) - -def wumpus(x, y): +def wumpus (x, y): return Expr('W', x, y) - def pit(x, y): return Expr('P', x, y) - def breeze(x, y): return Expr('B', x, y) - def stench(x, y): return Expr('S', x, y) - def wumpus_alive(time): return Expr('WumpusAlive', time) - def have_arrow(time): return Expr('HaveArrow', time) - def percept_stench(time): return Expr('Stench', time) - def percept_breeze(time): return Expr('Breeze', time) - def percept_glitter(time): return Expr('Glitter', time) - def percept_bump(time): return Expr('Bump', time) - def percept_scream(time): return Expr('Scream', time) - def move_forward(time): return Expr('Forward', time) - def shoot(time): return Expr('Shoot', time) - def turn_left(time): return Expr('TurnLeft', time) - def turn_right(time): return Expr('TurnRight', time) - def ok_to_move(x, y, time): return Expr('OK', x, y, time) - -def location(x, y, time=None): +def location(x, y, time = None): if time is None: return Expr('L', x, y) else: return Expr('L', x, y, time) - # Symbols def implies(lhs, rhs): return Expr('==>', lhs, rhs) - def equiv(lhs, rhs): return Expr('<=>', lhs, rhs) - # Helper Function def new_disjunction(sentences): t = sentences[0] - for i in range(1, len(sentences)): + for i in range(1,len(sentences)): t |= sentences[i] return t @@ -894,59 +812,62 @@ def new_disjunction(sentences): class WumpusKB(PropKB): """ - Create a Knowledge Base that contains the a temporal "Wumpus physics" and temporal rules with time zero. + Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. """ - def __init__(self, dimrow): + def __init__(self,dimrow): super().__init__() self.dimrow = dimrow - self.tell(~wumpus(1, 1)) - self.tell(~pit(1, 1)) + self.tell( ~wumpus(1, 1) ) + self.tell( ~pit(1, 1) ) - for y in range(1, dimrow + 1): - for x in range(1, dimrow + 1): + for y in range(1, dimrow+1): + for x in range(1, dimrow+1): pits_in = list() wumpus_in = list() - if x > 1: # West room exists + if x > 1: # West room exists pits_in.append(pit(x - 1, y)) wumpus_in.append(wumpus(x - 1, y)) - if y < dimrow: # North room exists + if y < dimrow: # North room exists pits_in.append(pit(x, y + 1)) wumpus_in.append(wumpus(x, y + 1)) - if x < dimrow: # East room exists + if x < dimrow: # East room exists pits_in.append(pit(x + 1, y)) wumpus_in.append(wumpus(x + 1, y)) - if y > 1: # South room exists + if y > 1: # South room exists pits_in.append(pit(x, y - 1)) wumpus_in.append(wumpus(x, y - 1)) self.tell(equiv(breeze(x, y), new_disjunction(pits_in))) self.tell(equiv(stench(x, y), new_disjunction(wumpus_in))) - # Rule that describes existence of at least one Wumpus + + ## Rule that describes existence of at least one Wumpus wumpus_at_least = list() - for x in range(1, dimrow + 1): + for x in range(1, dimrow+1): for y in range(1, dimrow + 1): wumpus_at_least.append(wumpus(x, y)) self.tell(new_disjunction(wumpus_at_least)) - # Rule that describes existence of at most one Wumpus - for i in range(1, dimrow + 1): - for j in range(1, dimrow + 1): - for u in range(1, dimrow + 1): - for v in range(1, dimrow + 1): - if i != u or j != v: + + ## Rule that describes existence of at most one Wumpus + for i in range(1, dimrow+1): + for j in range(1, dimrow+1): + for u in range(1, dimrow+1): + for v in range(1, dimrow+1): + if i!=u or j!=v: self.tell(~wumpus(i, j) | ~wumpus(u, v)) - # Temporal rules at time zero + + ## Temporal rules at time zero self.tell(location(1, 1, 0)) - for i in range(1, dimrow + 1): + for i in range(1, dimrow+1): for j in range(1, dimrow + 1): self.tell(implies(location(i, j, 0), equiv(percept_breeze(0), breeze(i, j)))) self.tell(implies(location(i, j, 0), equiv(percept_stench(0), stench(i, j)))) @@ -960,6 +881,7 @@ def __init__(self, dimrow): self.tell(~facing_south(0)) self.tell(~facing_west(0)) + def make_action_sentence(self, action, time): actions = [move_forward(time), shoot(time), turn_left(time), turn_right(time)] @@ -973,7 +895,7 @@ def make_percept_sentence(self, percept, time): # Glitter, Bump, Stench, Breeze, Scream flags = [0, 0, 0, 0, 0] - # Things perceived + ## Things perceived if isinstance(percept, Glitter): flags[0] = 1 self.tell(percept_glitter(time)) @@ -990,7 +912,7 @@ def make_percept_sentence(self, percept, time): flags[4] = 1 self.tell(percept_scream(time)) - # Things not perceived + ## Things not perceived for i in range(len(flags)): if flags[i] == 0: if i == 0: @@ -1004,14 +926,15 @@ def make_percept_sentence(self, percept, time): elif i == 4: self.tell(~percept_scream(time)) + def add_temporal_sentences(self, time): if time == 0: return t = time - 1 - # current location rules - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + ## current location rules + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): self.tell(implies(location(i, j, time), equiv(percept_breeze(time), breeze(i, j)))) self.tell(implies(location(i, j, time), equiv(percept_stench(time), stench(i, j)))) @@ -1033,15 +956,15 @@ def add_temporal_sentences(self, time): if j != self.dimrow: s.append(location(i, j + 1, t) & facing_south(t) & move_forward(t)) - # add sentence about location i,j + ## add sentence about location i,j self.tell(new_disjunction(s)) - # add sentence about safety of location i,j + ## add sentence about safety of location i,j self.tell( equiv(ok_to_move(i, j, time), ~pit(i, j) & ~wumpus(i, j) & wumpus_alive(time)) ) - # Rules about current orientation + ## Rules about current orientation a = facing_north(t) & turn_right(t) b = facing_south(t) & turn_left(t) @@ -1067,15 +990,16 @@ def add_temporal_sentences(self, time): s = equiv(facing_south(time), a | b | c) self.tell(s) - # Rules about last action + ## Rules about last action self.tell(equiv(move_forward(t), ~turn_right(t) & ~turn_left(t))) - # Rule about the arrow + ##Rule about the arrow self.tell(equiv(have_arrow(time), have_arrow(t) & ~shoot(t))) - # Rule about Wumpus (dead or alive) + ##Rule about Wumpus (dead or alive) self.tell(equiv(wumpus_alive(time), wumpus_alive(t) & ~percept_scream(time))) + def ask_if_true(self, query): return pl_resolution(self, query) @@ -1083,12 +1007,13 @@ def ask_if_true(self, query): # ______________________________________________________________________________ -class WumpusPosition: +class WumpusPosition(): def __init__(self, x, y, orientation): self.X = x self.Y = y self.orientation = orientation + def get_location(self): return self.X, self.Y @@ -1103,19 +1028,19 @@ def set_orientation(self, orientation): self.orientation = orientation def __eq__(self, other): - if other.get_location() == self.get_location() and other.get_orientation() == self.get_orientation(): + if other.get_location() == self.get_location() and \ + other.get_orientation()==self.get_orientation(): return True else: return False - # ______________________________________________________________________________ class HybridWumpusAgent(Agent): """An agent for the wumpus world that does logical inference. [Figure 7.20]""" - def __init__(self, dimentions): + def __init__(self,dimentions): self.dimrow = dimentions self.kb = WumpusKB(self.dimrow) self.t = 0 @@ -1123,14 +1048,15 @@ def __init__(self, dimentions): self.current_position = WumpusPosition(1, 1, 'UP') super().__init__(self.execute) + def execute(self, percept): self.kb.make_percept_sentence(percept, self.t) self.kb.add_temporal_sentences(self.t) temp = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if self.kb.ask_if_true(location(i, j, self.t)): temp.append(i) temp.append(j) @@ -1145,8 +1071,8 @@ def execute(self, percept): self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT') safe_points = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if self.kb.ask_if_true(ok_to_move(i, j, self.t)): safe_points.append([i, j]) @@ -1154,14 +1080,14 @@ def execute(self, percept): goals = list() goals.append([1, 1]) self.plan.append('Grab') - actions = self.plan_route(self.current_position, goals, safe_points) + actions = self.plan_route(self.current_position,goals,safe_points) self.plan.extend(actions) self.plan.append('Climb') if len(self.plan) == 0: unvisited = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): for k in range(self.t): if self.kb.ask_if_true(location(i, j, k)): unvisited.append([i, j]) @@ -1171,13 +1097,13 @@ def execute(self, percept): if u not in unvisited_and_safe and s == u: unvisited_and_safe.append(u) - temp = self.plan_route(self.current_position, unvisited_and_safe, safe_points) + temp = self.plan_route(self.current_position,unvisited_and_safe,safe_points) self.plan.extend(temp) if len(self.plan) == 0 and self.kb.ask_if_true(have_arrow(self.t)): possible_wumpus = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if not self.kb.ask_if_true(wumpus(i, j)): possible_wumpus.append([i, j]) @@ -1186,8 +1112,8 @@ def execute(self, percept): if len(self.plan) == 0: not_unsafe = list() - for i in range(1, self.dimrow + 1): - for j in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): + for j in range(1, self.dimrow+1): if not self.kb.ask_if_true(ok_to_move(i, j, self.t)): not_unsafe.append([i, j]) temp = self.plan_route(self.current_position, not_unsafe, safe_points) @@ -1207,17 +1133,19 @@ def execute(self, percept): return action + def plan_route(self, current, goals, allowed): problem = PlanRoute(current, goals, allowed, self.dimrow) return astar_search(problem).solution() + def plan_shot(self, current, goals, allowed): shooting_positions = set() for loc in goals: x = loc[0] y = loc[1] - for i in range(1, self.dimrow + 1): + for i in range(1, self.dimrow+1): if i < x: shooting_positions.add(WumpusPosition(i, y, 'EAST')) if i > x: @@ -1229,7 +1157,7 @@ def plan_shot(self, current, goals, allowed): # Can't have a shooting position from any of the rooms the Wumpus could reside orientations = ['EAST', 'WEST', 'NORTH', 'SOUTH'] - for loc in goals: + for loc in goals: for orientation in orientations: shooting_positions.remove(WumpusPosition(loc[0], loc[1], orientation)) @@ -1258,7 +1186,7 @@ def translate_to_SAT(init, transition, goal, time): # Symbol claiming state s at time t state_counter = itertools.count() for s in states: - for t in range(time + 1): + for t in range(time+1): state_sym[s, t] = Expr("State_{}".format(next(state_counter))) # Add initial state axiom @@ -1278,11 +1206,11 @@ def translate_to_SAT(init, transition, goal, time): "Transition_{}".format(next(transition_counter))) # Change the state from s to s_ - clauses.append(action_sym[s, action, t] | '==>' | state_sym[s, t]) - clauses.append(action_sym[s, action, t] | '==>' | state_sym[s_, t + 1]) + clauses.append(action_sym[s, action, t] |'==>'| state_sym[s, t]) + clauses.append(action_sym[s, action, t] |'==>'| state_sym[s_, t + 1]) # Allow only one state at any time - for t in range(time + 1): + for t in range(time+1): # must be a state at any time clauses.append(associate('|', [state_sym[s, t] for s in states])) @@ -1435,7 +1363,6 @@ def standardize_variables(sentence, dic=None): standardize_variables.counter = itertools.count() - # ______________________________________________________________________________ @@ -1477,7 +1404,6 @@ def fol_fc_ask(KB, alpha): """A simple forward-chaining algorithm. [Figure 9.3]""" # TODO: Improve efficiency kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)}) - def enum_subst(p): query_vars = list({v for clause in p for v in variables(clause)}) for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)): @@ -1540,8 +1466,8 @@ def fol_bc_and(KB, goals, theta): P11, P12, P21, P22, P31, B11, B21 = expr('P11, P12, P21, P22, P31, B11, B21') wumpus_kb.tell(~P11) -wumpus_kb.tell(B11 | '<=>' | (P12 | P21)) -wumpus_kb.tell(B21 | '<=>' | (P11 | P22 | P31)) +wumpus_kb.tell(B11 | '<=>' | ((P12 | P21))) +wumpus_kb.tell(B21 | '<=>' | ((P11 | P22 | P31))) wumpus_kb.tell(~B11) wumpus_kb.tell(B21) @@ -1571,7 +1497,6 @@ def fol_bc_and(KB, goals, theta): 'Enemy(Nono, America)' ])) - # ______________________________________________________________________________ # Example application (not in the book). @@ -1602,7 +1527,7 @@ def diff(y, x): elif op == '/': return (v * diff(u, x) - u * diff(v, x)) / (v * v) elif op == '**' and isnumber(x.op): - return v * u ** (v - 1) * diff(u, x) + return (v * u ** (v - 1) * diff(u, x)) elif op == '**': return (v * u ** (v - 1) * diff(u, x) + u ** v * Expr('log')(u) * diff(v, x)) diff --git a/tests/test_csp.py b/tests/test_csp.py index a7564a395..c34d42540 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -10,16 +10,16 @@ def test_csp_assign(): var = 10 val = 5 assignment = {} - australia_csp.assign(var, val, assignment) + australia.assign(var, val, assignment) - assert australia_csp.nassigns == 1 + assert australia.nassigns == 1 assert assignment[var] == val def test_csp_unassign(): var = 10 assignment = {var: 5} - australia_csp.unassign(var, assignment) + australia.unassign(var, assignment) assert var not in assignment @@ -330,22 +330,22 @@ def test_forward_checking(): def test_backtracking_search(): - assert backtracking_search(australia_csp) - assert backtracking_search(australia_csp, select_unassigned_variable=mrv) - assert backtracking_search(australia_csp, order_domain_values=lcv) - assert backtracking_search(australia_csp, select_unassigned_variable=mrv, + assert backtracking_search(australia) + assert backtracking_search(australia, select_unassigned_variable=mrv) + assert backtracking_search(australia, order_domain_values=lcv) + assert backtracking_search(australia, select_unassigned_variable=mrv, order_domain_values=lcv) - assert backtracking_search(australia_csp, inference=forward_checking) - assert backtracking_search(australia_csp, inference=mac) - assert backtracking_search(usa_csp, select_unassigned_variable=mrv, + assert backtracking_search(australia, inference=forward_checking) + assert backtracking_search(australia, inference=mac) + assert backtracking_search(usa, select_unassigned_variable=mrv, order_domain_values=lcv, inference=mac) def test_min_conflicts(): - assert min_conflicts(australia_csp) - assert min_conflicts(france_csp) + assert min_conflicts(australia) + assert min_conflicts(france) - tests = [(usa_csp, None)] * 3 + tests = [(usa, None)] * 3 assert failure_test(min_conflicts, tests) >= 1 / 3 australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') @@ -418,7 +418,7 @@ def test_parse_neighbours(): def test_topological_sort(): root = 'NT' - Sort, Parents = topological_sort(australia_csp, root) + Sort, Parents = topological_sort(australia, root) assert Sort == ['NT', 'SA', 'Q', 'NSW', 'V', 'WA'] assert Parents['NT'] == None diff --git a/tests/test_logic.py b/tests/test_logic.py index 2d4468d0d..378f1f0fc 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -1,12 +1,10 @@ import pytest - from logic import * -from utils import expr_handle_infix_ops, count +from utils import expr_handle_infix_ops, count, Symbol definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', - 'C']: - definite_clauses_KB.tell(expr(clause)) +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: + definite_clauses_KB.tell(expr(clause)) def test_is_symbol(): @@ -49,7 +47,7 @@ def test_extend(): def test_subst(): - assert subst({x: 42, y: 0}, F(x) + y) == (F(42) + 0) + assert subst({x: 42, y:0}, F(x) + y) == (F(42) + 0) def test_PropKB(): @@ -57,7 +55,7 @@ def test_PropKB(): assert count(kb.ask(expr) for expr in [A, C, D, E, Q]) is 0 kb.tell(A & E) assert kb.ask(A) == kb.ask(E) == {} - kb.tell(E | '==>' | C) + kb.tell(E |'==>'| C) assert kb.ask(C) == {} kb.retract(E) assert kb.ask(E) is False @@ -96,8 +94,7 @@ def test_is_definite_clause(): def test_parse_definite_clause(): assert parse_definite_clause(expr('A & B & C & D ==> E')) == ([A, B, C, D], E) assert parse_definite_clause(expr('Farmer(Mac)')) == ([], expr('Farmer(Mac)')) - assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ( - [expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) + assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ([expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) def test_pl_true(): @@ -134,28 +131,28 @@ def test_dpll(): assert (dpll_satisfiable(A & ~B & C & (A | ~D) & (~E | ~D) & (C | ~D) & (~A | ~F) & (E | ~F) & (~D | ~F) & (B | ~C | D) & (A | ~E | F) & (~A | E | D)) == {B: False, C: True, A: True, F: False, D: True, E: False}) - assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} - assert dpll_satisfiable((A | (B & C)) | '<=>' | ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} - assert dpll_satisfiable(A | '<=>' | B) == {A: True, B: True} + assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} + assert dpll_satisfiable((A | (B & C)) |'<=>'| ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} + assert dpll_satisfiable(A |'<=>'| B) == {A: True, B: True} assert dpll_satisfiable(A & ~B) == {A: True, B: False} assert dpll_satisfiable(P & ~P) is False def test_find_pure_symbol(): - assert find_pure_symbol([A, B, C], [A | ~B, ~B | ~C, C | A]) == (A, True) - assert find_pure_symbol([A, B, C], [~A | ~B, ~B | ~C, C | A]) == (B, False) - assert find_pure_symbol([A, B, C], [~A | B, ~B | ~C, C | A]) == (None, None) + assert find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A]) == (A, True) + assert find_pure_symbol([A, B, C], [~A|~B,~B|~C,C|A]) == (B, False) + assert find_pure_symbol([A, B, C], [~A|B,~B|~C,C|A]) == (None, None) def test_unit_clause_assign(): - assert unit_clause_assign(A | B | C, {A: True}) == (None, None) - assert unit_clause_assign(B | C, {A: True}) == (None, None) - assert unit_clause_assign(B | ~A, {A: True}) == (B, True) + assert unit_clause_assign(A|B|C, {A:True}) == (None, None) + assert unit_clause_assign(B|C, {A:True}) == (None, None) + assert unit_clause_assign(B|~A, {A:True}) == (B, True) def test_find_unit_clause(): - assert find_unit_clause([A | B | C, B | ~C, ~A | ~B], {A: True}) == (B, False) - + assert find_unit_clause([A|B|C, B|~C, ~A|~B], {A:True}) == (B, False) + def test_unify(): assert unify(x, x, {}) == {} @@ -178,9 +175,9 @@ def test_tt_entails(): assert tt_entails(P & Q, Q) assert not tt_entails(P | Q, Q) assert tt_entails(A & (B | C) & E & F & ~(P | Q), A & E & F & ~P & ~Q) - assert not tt_entails(P | '<=>' | Q, Q) - assert tt_entails((P | '==>' | Q) & P, Q) - assert not tt_entails((P | '<=>' | Q) & ~P, Q) + assert not tt_entails(P |'<=>'| Q, Q) + assert tt_entails((P |'==>'| Q) & P, Q) + assert not tt_entails((P |'<=>'| Q) & ~P, Q) def test_prop_symbols(): @@ -234,13 +231,12 @@ def test_move_not_inwards(): def test_distribute_and_over_or(): - def test_entailment(s, has_and=False): + def test_entailment(s, has_and = False): result = distribute_and_over_or(s) if has_and: assert result.op == '&' assert tt_entails(s, result) assert tt_entails(result, s) - test_entailment((A & B) | C, True) test_entailment((A | B) & C, True) test_entailment((A | B) | C, False) @@ -257,8 +253,7 @@ def test_to_cnf(): assert repr(to_cnf("a | (b & c) | d")) == '((b | a | d) & (c | a | d))' assert repr(to_cnf("A & (B | (D & E))")) == '(A & (D | B) & (E | B))' assert repr(to_cnf("A | (B | (C | (D & E)))")) == '((D | A | B | C) & (E | A | B | C))' - assert repr(to_cnf( - '(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' + assert repr(to_cnf('(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' def test_pl_resolution(): @@ -286,7 +281,6 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) - assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]' @@ -301,7 +295,6 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) - assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' assert repr(test_ask('Enemy(x, America)', crime_kb)) == '[{x: Nono}]' assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' @@ -323,7 +316,6 @@ def check_SAT(clauses, single_solution={}): if single_solution: # Cross check the solution if only one exists assert all(pl_true(x, single_solution) for x in clauses) assert soln == single_solution - # Test WalkSat for problems with solution check_SAT([A & B, A & C]) check_SAT([A | B, P & Q, P & B]) From e13090985f20eccd87b3364d5f61d8da08ff9586 Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Mon, 29 Jul 2019 12:54:32 +0200 Subject: [PATCH 18/21] Revert "fixed build error" This reverts commit 6641c2c861728f3d43d3931ef201c6f7093cbc96. --- agents_4e.py | 81 ++++++++++--------------- tests/test_agents_4e.py | 129 +++++++++++++++++++--------------------- 2 files changed, 94 insertions(+), 116 deletions(-) diff --git a/agents_4e.py b/agents_4e.py index b357f5251..debd9441e 100644 --- a/agents_4e.py +++ b/agents_4e.py @@ -113,11 +113,9 @@ def new_program(percept): action = old_program(percept) print('{} perceives {} and does {}'.format(agent, percept, action)) return action - agent.program = new_program return agent - # ______________________________________________________________________________ @@ -132,7 +130,6 @@ def program(percept): percepts.append(percept) action = table.get(tuple(percepts)) return action - return program @@ -149,31 +146,26 @@ def RandomAgentProgram(actions): """ return lambda percept: random.choice(actions) - # ______________________________________________________________________________ def SimpleReflexAgentProgram(rules, interpret_input): """This agent takes action based solely on the percept. [Figure 2.10]""" - def program(percept): state = interpret_input(percept) rule = rule_match(state, rules) action = rule.action return action - return program def ModelBasedReflexAgentProgram(rules, update_state, trainsition_model, sensor_model): """This agent takes action based on the percept and state. [Figure 2.12]""" - def program(percept): program.state = update_state(program.state, program.action, percept, trainsition_model, sensor_model) rule = rule_match(program.state, rules) action = rule.action return action - program.state = program.action = None return program @@ -184,7 +176,6 @@ def rule_match(state, rules): if rule.matches(state): return rule - # ______________________________________________________________________________ @@ -228,7 +219,6 @@ def ReflexVacuumAgent(): >>> environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} True """ - def program(percept): location, status = percept if status == 'Dirty': @@ -237,7 +227,6 @@ def program(percept): return 'Right' elif location == loc_B: return 'Left' - return Agent(program) @@ -264,10 +253,8 @@ def program(percept): return 'Right' elif location == loc_B: return 'Left' - return Agent(program) - # ______________________________________________________________________________ @@ -405,22 +392,22 @@ def __add__(self, heading): True """ if self.direction == self.R: - return { + return{ self.R: Direction(self.D), self.L: Direction(self.U), }.get(heading, None) elif self.direction == self.L: - return { + return{ self.R: Direction(self.U), self.L: Direction(self.D), }.get(heading, None) elif self.direction == self.U: - return { + return{ self.R: Direction(self.R), self.L: Direction(self.L), }.get(heading, None) elif self.direction == self.D: - return { + return{ self.R: Direction(self.L), self.L: Direction(self.R), }.get(heading, None) @@ -438,13 +425,13 @@ def move_forward(self, from_location): """ x, y = from_location if self.direction == self.R: - return x + 1, y + return (x + 1, y) elif self.direction == self.L: - return x - 1, y + return (x - 1, y) elif self.direction == self.U: - return x, y - 1 + return (x, y - 1) elif self.direction == self.D: - return x, y + 1 + return (x, y + 1) class XYEnvironment(Environment): @@ -475,7 +462,7 @@ def things_near(self, location, radius=None): radius2 = radius * radius return [(thing, radius2 - distance_squared(location, thing.location)) for thing in self.things if distance_squared( - location, thing.location) <= radius2] + location, thing.location) <= radius2] def percept(self, agent): """By default, agent perceives things within a default radius.""" @@ -489,17 +476,17 @@ def execute_action(self, agent, action): agent.direction += Direction.L elif action == 'Forward': agent.bump = self.move_to(agent, agent.direction.move_forward(agent.location)) - # elif action == 'Grab': - # things = [thing for thing in self.list_things_at(agent.location) - # if agent.can_grab(thing)] - # if things: - # agent.holding.append(things[0]) +# elif action == 'Grab': +# things = [thing for thing in self.list_things_at(agent.location) +# if agent.can_grab(thing)] +# if things: +# agent.holding.append(things[0]) elif action == 'Release': if agent.holding: agent.holding.pop() def default_location(self, thing): - return random.choice(self.width), random.choice(self.height) + return (random.choice(self.width), random.choice(self.height)) def move_to(self, thing, destination): """Move a thing to a new location. Returns True on success or False if there is an Obstacle. @@ -518,7 +505,7 @@ def move_to(self, thing, destination): def add_thing(self, thing, location=(1, 1), exclude_duplicate_class_items=False): """Add things to the world. If (exclude_duplicate_class_items) then the item won't be added if the location has at least one item of the same class.""" - if self.is_inbounds(location): + if (self.is_inbounds(location)): if (exclude_duplicate_class_items and any(isinstance(t, thing.__class__) for t in self.list_things_at(location))): return @@ -527,14 +514,14 @@ def add_thing(self, thing, location=(1, 1), exclude_duplicate_class_items=False) def is_inbounds(self, location): """Checks to make sure that the location is inbounds (within walls if we have walls)""" x, y = location - return not (x < self.x_start or x > self.x_end or y < self.y_start or y > self.y_end) + return not (x < self.x_start or x >= self.x_end or y < self.y_start or y >= self.y_end) def random_location_inbounds(self, exclude=None): """Returns a random location that is inbounds (within walls if we have walls)""" location = (random.randint(self.x_start, self.x_end), random.randint(self.y_start, self.y_end)) if exclude is not None: - while location == exclude: + while(location == exclude): location = (random.randint(self.x_start, self.x_end), random.randint(self.y_start, self.y_end)) return location @@ -556,7 +543,7 @@ def add_walls(self): for x in range(self.width): self.add_thing(Wall(), (x, 0)) self.add_thing(Wall(), (x, self.height - 1)) - for y in range(1, self.height - 1): + for y in range(1, self.height-1): self.add_thing(Wall(), (0, y)) self.add_thing(Wall(), (self.width - 1, y)) @@ -587,7 +574,6 @@ class Obstacle(Thing): class Wall(Obstacle): pass - # ______________________________________________________________________________ @@ -696,7 +682,6 @@ def __init__(self, coordinates): super().__init__() self.coordinates = coordinates - # ______________________________________________________________________________ # Vacuum environment @@ -706,6 +691,7 @@ class Dirt(Thing): class VacuumEnvironment(XYEnvironment): + """The environment of [Ex. 2.12]. Agent perceives dirty or clean, and bump (into obstacle) or not; 2D discrete world of unknown size; performance measure is 100 for each dirt cleaned, and -1 for @@ -724,7 +710,7 @@ def percept(self, agent): Unlike the TrivialVacuumEnvironment, location is NOT perceived.""" status = ('Dirty' if self.some_things_at( agent.location, Dirt) else 'Clean') - bump = ('Bump' if agent.bump else 'None') + bump = ('Bump' if agent.bump else'None') return (status, bump) def execute_action(self, agent, action): @@ -743,6 +729,7 @@ def execute_action(self, agent, action): class TrivialVacuumEnvironment(Environment): + """This environment has two locations, A and B. Each can be Dirty or Clean. The agent perceives its location and the location's status. This serves as an example of how to implement a simple @@ -779,7 +766,6 @@ def default_location(self, thing): """Agents start in either location at random.""" return random.choice([loc_A, loc_B]) - # ______________________________________________________________________________ # The Wumpus World @@ -789,7 +775,6 @@ class Gold(Thing): def __eq__(self, rhs): """All Gold are equal""" return rhs.__class__ == Gold - pass @@ -839,7 +824,6 @@ def can_grab(self, thing): class WumpusEnvironment(XYEnvironment): pit_probability = 0.2 # Probability to spawn a pit in a location. (From Chapter 7.2) - # Room should be 4x4 grid of rooms. The extra 2 for walls def __init__(self, agent_program, width=6, height=6): @@ -917,9 +901,12 @@ def percept(self, agent): """Return things in adjacent (not diagonal) cells of the agent. Result format: [Left, Right, Up, Down, Center / Current location]""" x, y = agent.location - result = [self.percepts_from(agent, (x - 1, y)), self.percepts_from(agent, (x + 1, y)), - self.percepts_from(agent, (x, y - 1)), self.percepts_from(agent, (x, y + 1)), - self.percepts_from(agent, (x, y))] + result = [] + result.append(self.percepts_from(agent, (x - 1, y))) + result.append(self.percepts_from(agent, (x + 1, y))) + result.append(self.percepts_from(agent, (x, y - 1))) + result.append(self.percepts_from(agent, (x, y + 1))) + result.append(self.percepts_from(agent, (x, y))) """The wumpus gives out a loud scream once it's killed.""" wumpus = [thing for thing in self.things if isinstance(thing, Wumpus)] @@ -962,7 +949,7 @@ def execute_action(self, agent, action): """The arrow travels straight down the path the agent is facing""" if agent.has_arrow: arrow_travel = agent.direction.move_forward(agent.location) - while self.is_inbounds(arrow_travel): + while(self.is_inbounds(arrow_travel)): wumpus = [thing for thing in self.list_things_at(arrow_travel) if isinstance(thing, Wumpus)] if len(wumpus): @@ -992,13 +979,12 @@ def is_done(self): print("Death by {} [-1000].".format(explorer[0].killed_by)) else: print("Explorer climbed out {}." - .format( - "with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) + .format( + "with Gold [+1000]!" if Gold() not in self.things else "without Gold [+0]")) return True - # TODO: Arrow needs to be implemented - + # TODO: Arrow needs to be implemented # ______________________________________________________________________________ @@ -1030,16 +1016,13 @@ def test_agent(AgentFactory, steps, envs): >>> result == 5 True """ - def score(env): agent = AgentFactory() env.add_thing(agent) env.run(steps) return agent.performance - return mean(map(score, envs)) - # _________________________________________________________________________ diff --git a/tests/test_agents_4e.py b/tests/test_agents_4e.py index 3ebc258cb..ca082887e 100644 --- a/tests/test_agents_4e.py +++ b/tests/test_agents_4e.py @@ -1,12 +1,12 @@ import random - -from agents_4e import Agent from agents_4e import Direction -from agents_4e import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents, \ - RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ - SimpleReflexAgentProgram, ModelBasedReflexAgentProgram +from agents_4e import Agent +from agents_4e import ReflexVacuumAgent, ModelBasedVacuumAgent, TrivialVacuumEnvironment, compare_agents,\ + RandomVacuumAgent, TableDrivenVacuumAgent, TableDrivenAgentProgram, RandomAgentProgram, \ + SimpleReflexAgentProgram, ModelBasedReflexAgentProgram, rule_match from agents_4e import Wall, Gold, Explorer, Thing, Bump, Glitter, WumpusEnvironment, Pit, \ - VacuumEnvironment, Dirt + VacuumEnvironment, Dirt + random.seed("aima-python") @@ -58,12 +58,12 @@ def test_add(): assert l2.direction == Direction.D -def test_RandomAgentProgram(): - # create a list of all the actions a vacuum cleaner can perform +def test_RandomAgentProgram() : + #create a list of all the actions a vacuum cleaner can perform list = ['Right', 'Left', 'Suck', 'NoOp'] # create a program and then an object of the RandomAgentProgram program = RandomAgentProgram(list) - + agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -72,10 +72,10 @@ def test_RandomAgentProgram(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1, 0): 'Clean' , (0, 0): 'Clean'} -def test_RandomVacuumAgent(): +def test_RandomVacuumAgent() : # create an object of the RandomVacuumAgent agent = RandomVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -85,7 +85,7 @@ def test_RandomVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} def test_TableDrivenAgent(): @@ -109,21 +109,22 @@ def test_TableDrivenAgent(): # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() # initializing some environment status - environment.status = {loc_A: 'Dirty', loc_B: 'Dirty'} + environment.status = {loc_A:'Dirty', loc_B:'Dirty'} # add agent to the environment - environment.add_thing(agent, location=(1, 0)) + environment.add_thing(agent) + # run the environment by single step everytime to check how environment evolves using TableDrivenAgentProgram - environment.run(steps=1) - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} - environment.run(steps=1) - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Dirty'} + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Dirty'} - environment.run(steps=1) - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + environment.run(steps = 1) + assert environment.status == {(1,0): 'Clean', (0,0): 'Clean'} -def test_ReflexVacuumAgent(): +def test_ReflexVacuumAgent() : # create an object of the ReflexVacuumAgent agent = ReflexVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -133,31 +134,31 @@ def test_ReflexVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} def test_SimpleReflexAgentProgram(): class Rule: - + def __init__(self, state, action): self.__state = state self.action = action - + def matches(self, state): return self.__state == state - + loc_A = (0, 0) loc_B = (1, 0) - + # create rules for a two state Vacuum Environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] - + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + def interpret_input(state): return state - + # create a program and then an object of the SimpleReflexAgentProgram - program = SimpleReflexAgentProgram(rules, interpret_input) + program = SimpleReflexAgentProgram(rules, interpret_input) agent = Agent(program) # create an object of TrivialVacuumEnvironment environment = TrivialVacuumEnvironment() @@ -166,7 +167,7 @@ def interpret_input(state): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} def test_ModelBasedReflexAgentProgram(): @@ -184,7 +185,7 @@ def matches(self, state): # create rules for a two-state vacuum environment rules = [Rule((loc_A, "Dirty"), "Suck"), Rule((loc_A, "Clean"), "Right"), - Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] + Rule((loc_B, "Dirty"), "Suck"), Rule((loc_B, "Clean"), "Left")] def update_state(state, action, percept, transition_model, sensor_model): return percept @@ -202,7 +203,7 @@ def update_state(state, action, percept, transition_model, sensor_model): assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} -def test_ModelBasedVacuumAgent(): +def test_ModelBasedVacuumAgent() : # create an object of the ModelBasedVacuumAgent agent = ModelBasedVacuumAgent() # create an object of TrivialVacuumEnvironment @@ -212,10 +213,10 @@ def test_ModelBasedVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1,0):'Clean' , (0,0) : 'Clean'} -def test_TableDrivenVacuumAgent(): +def test_TableDrivenVacuumAgent() : # create an object of the TableDrivenVacuumAgent agent = TableDrivenVacuumAgent() # create an object of the TrivialVacuumEnvironment @@ -225,10 +226,10 @@ def test_TableDrivenVacuumAgent(): # run the environment environment.run() # check final status of the environment - assert environment.status == {(1, 0): 'Clean', (0, 0): 'Clean'} + assert environment.status == {(1, 0):'Clean', (0, 0):'Clean'} -def test_compare_agents(): +def test_compare_agents() : environment = TrivialVacuumEnvironment agents = [ModelBasedVacuumAgent, ReflexVacuumAgent] @@ -262,26 +263,24 @@ def test_TableDrivenAgentProgram(): def test_Agent(): def constant_prog(percept): return percept - agent = Agent(constant_prog) result = agent.program(5) assert result == 5 - def test_VacuumEnvironment(): # Initialize Vacuum Environment - v = VacuumEnvironment(6, 6) - # Get an agent + v = VacuumEnvironment(6,6) + #Get an agent agent = ModelBasedVacuumAgent() agent.direction = Direction(Direction.R) v.add_thing(agent) - v.add_thing(Dirt(), location=(2, 1)) + v.add_thing(Dirt(), location=(2,1)) # Check if things are added properly assert len([x for x in v.things if isinstance(x, Wall)]) == 20 assert len([x for x in v.things if isinstance(x, Dirt)]) == 1 - # Let the action begin! + #Let the action begin! assert v.percept(agent) == ("Clean", "None") v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "None") @@ -289,69 +288,65 @@ def test_VacuumEnvironment(): v.execute_action(agent, "Forward") assert v.percept(agent) == ("Dirty", "Bump") v.execute_action(agent, "Suck") - assert v.percept(agent) == ("Clean", "None") + assert v.percept(agent) == ("Clean", "None") old_performance = agent.performance v.execute_action(agent, "NoOp") assert old_performance == agent.performance - def test_WumpusEnvironment(): def constant_prog(percept): return percept - # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) - # Check if things are added properly + #Check if things are added properly assert len([x for x in w.things if isinstance(x, Wall)]) == 20 assert any(map(lambda x: isinstance(x, Gold), w.things)) assert any(map(lambda x: isinstance(x, Explorer), w.things)) - assert not any(map(lambda x: not isinstance(x, Thing), w.things)) + assert not any(map(lambda x: not isinstance(x,Thing), w.things)) - # Check that gold and wumpus are not present on (1,1) - assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x, WumpusEnvironment), - w.list_things_at((1, 1)))) + #Check that gold and wumpus are not present on (1,1) + assert not any(map(lambda x: isinstance(x, Gold) or isinstance(x,WumpusEnvironment), + w.list_things_at((1, 1)))) - # Check if w.get_world() segments objects correctly + #Check if w.get_world() segments objects correctly assert len(w.get_world()) == 6 for row in w.get_world(): assert len(row) == 6 - # Start the game! + #Start the game! agent = [x for x in w.things if isinstance(x, Explorer)][0] gold = [x for x in w.things if isinstance(x, Gold)][0] pit = [x for x in w.things if isinstance(x, Pit)][0] - assert not w.is_done() + assert w.is_done()==False - # Check Walls + #Check Walls agent.location = (1, 2) percepts = w.percept(agent) assert len(percepts) == 5 - assert any(map(lambda x: isinstance(x, Bump), percepts[0])) + assert any(map(lambda x: isinstance(x,Bump), percepts[0])) - # Check Gold + #Check Gold agent.location = gold.location percepts = w.percept(agent) - assert any(map(lambda x: isinstance(x, Glitter), percepts[4])) - agent.location = (gold.location[0], gold.location[1] + 1) + assert any(map(lambda x: isinstance(x,Glitter), percepts[4])) + agent.location = (gold.location[0], gold.location[1]+1) percepts = w.percept(agent) - assert not any(map(lambda x: isinstance(x, Glitter), percepts[4])) + assert not any(map(lambda x: isinstance(x,Glitter), percepts[4])) - # Check agent death + #Check agent death agent.location = pit.location - assert w.in_danger(agent) - assert not agent.alive + assert w.in_danger(agent) == True + assert agent.alive == False assert agent.killed_by == Pit.__name__ assert agent.performance == -1000 - assert w.is_done() - + assert w.is_done()==True def test_WumpusEnvironmentActions(): def constant_prog(percept): return percept - # Initialize Wumpus Environment w = WumpusEnvironment(constant_prog) @@ -376,4 +371,4 @@ def constant_prog(percept): w.execute_action(agent, 'Climb') assert not any(map(lambda x: isinstance(x, Explorer), w.things)) - assert w.is_done() + assert w.is_done()==True \ No newline at end of file From 2f627767f6df38b40654d447cb67688df9b71c57 Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Mon, 29 Jul 2019 13:29:12 +0200 Subject: [PATCH 19/21] added map coloring SAT problem --- csp.py | 73 ++++++++------ logic.py | 232 +++++++++++++++++++++++++++++--------------- tests/test_csp.py | 28 +++--- tests/test_logic.py | 56 ++++++----- 4 files changed, 244 insertions(+), 145 deletions(-) diff --git a/csp.py b/csp.py index ee59d4a6b..4c1203f4a 100644 --- a/csp.py +++ b/csp.py @@ -74,10 +74,12 @@ def unassign(self, var, assignment): def nconflicts(self, var, val, assignment): """Return the number of conflicts var=val has with other variables.""" + # Subclasses may implement this more efficiently def conflict(var2): return (var2 in assignment and not self.constraints(var, val, var2, assignment[var2])) + return count(conflict(v) for v in self.neighbors[var]) def display(self, assignment): @@ -153,6 +155,7 @@ def conflicted_vars(self, current): return [var for var in self.variables if self.nconflicts(var, current[var], current) > 0] + # ______________________________________________________________________________ # Constraint Propagation with AC-3 @@ -183,6 +186,7 @@ def revise(csp, Xi, Xj, removals): revised = True return revised + # ______________________________________________________________________________ # CSP Backtracking Search @@ -208,6 +212,7 @@ def num_legal_values(csp, var, assignment): return count(csp.nconflicts(var, val, assignment) == 0 for val in csp.domains[var]) + # Value ordering @@ -221,6 +226,7 @@ def lcv(var, assignment, csp): return sorted(csp.choices(var), key=lambda val: csp.nconflicts(var, val, assignment)) + # Inference @@ -245,6 +251,7 @@ def mac(csp, var, value, assignment, removals): """Maintain arc consistency.""" return AC3(csp, {(X, var) for X in csp.neighbors[var]}, removals) + # The search, proper @@ -274,6 +281,7 @@ def backtrack(assignment): assert result is None or csp.goal_test(result) return result + # ______________________________________________________________________________ # Min-conflicts hillclimbing search for CSPs @@ -302,6 +310,7 @@ def min_conflicts_value(csp, var, current): return argmin_random_tie(csp.domains[var], key=lambda val: csp.nconflicts(var, val, current)) + # ______________________________________________________________________________ @@ -356,7 +365,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents): visited[node] = True for n in neighbors[node]: - if(not visited[n]): + if (not visited[n]): build_topological(n, node, neighbors, visited, stack, parents) parents[node] = parent @@ -366,9 +375,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents): def make_arc_consistent(Xj, Xk, csp): """Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints, by removing the possible values of Xj that cause inconsistencies.""" - #csp.curr_domains[Xj] = [] + # csp.curr_domains[Xj] = [] for val1 in csp.domains[Xj]: - keep = False # Keep or remove val1 + keep = False # Keep or remove val1 for val2 in csp.domains[Xk]: if csp.constraints(Xj, val1, Xk, val2): # Found a consistent assignment for val1, keep it @@ -393,8 +402,9 @@ def assign_value(Xj, Xk, csp, assignment): # No consistent assignment available return None + # ______________________________________________________________________________ -# Map-Coloring Problems +# Map Coloring Problems class UniversalDict: @@ -446,27 +456,27 @@ def parse_neighbors(neighbors, variables=None): return dic -australia = MapColoringCSP(list('RGB'), - 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') - -usa = MapColoringCSP(list('RGBY'), - """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; - UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; - ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; - TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; - LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; - MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; - PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; - NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; - HI: ; AK: """) - -france = MapColoringCSP(list('RGBY'), - """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA - AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO - CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: - MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: - PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: - AU BO FC PA LR""") +australia_csp = MapColoringCSP(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) + +usa_csp = MapColoringCSP(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + +france_csp = MapColoringCSP(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") + # ______________________________________________________________________________ # n-Queens Problem @@ -503,16 +513,16 @@ def __init__(self, n): CSP.__init__(self, list(range(n)), UniversalDict(list(range(n))), UniversalDict(list(range(n))), queen_constraint) - self.rows = [0]*n - self.ups = [0]*(2*n - 1) - self.downs = [0]*(2*n - 1) + self.rows = [0] * n + self.ups = [0] * (2 * n - 1) + self.downs = [0] * (2 * n - 1) def nconflicts(self, var, val, assignment): """The number of conflicts, as recorded with each assignment. Count conflicts in row and in up, down diagonals. If there is a queen there, it can't conflict with itself, so subtract 3.""" n = len(self.variables) - c = self.rows[val] + self.downs[var+val] + self.ups[var-val+n-1] + c = self.rows[val] + self.downs[var + val] + self.ups[var - val + n - 1] if assignment.get(var, None) == val: c -= 3 return c @@ -560,6 +570,7 @@ def display(self, assignment): print(str(self.nconflicts(var, val, assignment)) + ch, end=' ') print() + # ______________________________________________________________________________ # Sudoku @@ -646,9 +657,12 @@ def show_cell(cell): return str(assignment.get(cell, '.')) def abut(lines1, lines2): return list( map(' | '.join, list(zip(lines1, lines2)))) + print('\n------+-------+------\n'.join( '\n'.join(reduce( abut, map(show_box, brow))) for brow in self.bgrid)) + + # ______________________________________________________________________________ # The Zebra Puzzle @@ -716,6 +730,7 @@ def zebra_constraint(A, a, B, b, recurse=0): (A in Smokes and B in Smokes)): return not same raise Exception('error') + return CSP(variables, domains, neighbors, zebra_constraint) diff --git a/logic.py b/logic.py index 6aacc4f95..24736c1a9 100644 --- a/logic.py +++ b/logic.py @@ -30,7 +30,7 @@ unify Do unification of two FOL sentences diff, simp Symbolic differentiation and simplification """ - +from csp import parse_neighbors, UniversalDict from utils import ( removeall, unique, first, argmax, probability, isnumber, issequence, Expr, expr, subexpressions @@ -42,11 +42,11 @@ import random from collections import defaultdict + # ______________________________________________________________________________ class KB: - """A knowledge base to which you can tell and ask sentences. To create a KB, first subclass this class and implement tell, ask_generator, and retract. Why ask_generator instead of ask? @@ -106,6 +106,7 @@ def retract(self, sentence): if c in self.clauses: self.clauses.remove(c) + # ______________________________________________________________________________ @@ -319,6 +320,7 @@ def pl_true(exp, model={}): else: raise ValueError("illegal operator in logic expression" + str(exp)) + # ______________________________________________________________________________ # Convert to Conjunctive Normal Form (CNF) @@ -368,6 +370,7 @@ def move_not_inwards(s): if s.op == '~': def NOT(b): return move_not_inwards(~b) + a = s.args[0] if a.op == '~': return move_not_inwards(a.args[0]) # ~~A ==> A @@ -445,6 +448,7 @@ def collect(subargs): collect(arg.args) else: result.append(arg) + collect(args) return result @@ -468,6 +472,7 @@ def disjuncts(s): """ return dissociate('|', [s]) + # ______________________________________________________________________________ @@ -481,7 +486,7 @@ def pl_resolution(KB, alpha): while True: n = len(clauses) pairs = [(clauses[i], clauses[j]) - for i in range(n) for j in range(i+1, n)] + for i in range(n) for j in range(i + 1, n)] for (ci, cj) in pairs: resolvents = pl_resolve(ci, cj) if False in resolvents: @@ -505,6 +510,7 @@ def pl_resolve(ci, cj): clauses.append(associate('|', dnew)) return clauses + # ______________________________________________________________________________ @@ -560,7 +566,6 @@ def pl_fc_entails(KB, q): """ wumpus_world_inference = expr("(B11 <=> (P12 | P21)) & ~B11") - """ [Figure 7.16] Propositional Logic Forward Chaining example """ @@ -572,9 +577,11 @@ def pl_fc_entails(KB, q): Definite clauses KB example """ definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', + 'C']: definite_clauses_KB.tell(expr(clause)) + # ______________________________________________________________________________ # DPLL-Satisfiable [Figure 7.17] @@ -665,7 +672,7 @@ def unit_clause_assign(clause, model): if model[sym] == positive: return None, None # clause already True elif P: - return None, None # more than 1 unbound variable + return None, None # more than 1 unbound variable else: P, value = sym, positive return P, value @@ -684,6 +691,7 @@ def inspect_literal(literal): else: return literal, True + # ______________________________________________________________________________ # Walk-SAT [Figure 7.18] @@ -714,95 +722,169 @@ def sat_count(sym): count = len([clause for clause in clauses if pl_true(clause, model)]) model[sym] = not model[sym] return count + sym = argmax(prop_symbols(clause), key=sat_count) model[sym] = not model[sym] # If no solution is found within the flip limit, we return failure return None + +# ______________________________________________________________________________ +# Map Coloring Problems + + +def MapColoringSAT(colors, neighbors): + """Make a SAT for the problem of coloring a map with different colors + for any two adjacent regions. Arguments are a list of colors, and a + dict of {region: [neighbor,...]} entries. This dict may also be + specified as a string of the form defined by parse_neighbors.""" + if isinstance(neighbors, str): + neighbors = parse_neighbors(neighbors) + colors = UniversalDict(colors) + clauses = [] + for state in neighbors.keys(): + clause = [expr(state + '_' + c) for c in colors[state]] + clauses.append(clause) + for t in itertools.combinations(clause, 2): + clauses.append([~t[0], ~t[1]]) + visited = set() + adj = set(neighbors[state]) - visited + visited.add(state) + for n_state in adj: + for col in colors[n_state]: + clauses.append([expr('~' + state + '_' + col), expr('~' + n_state + '_' + col)]) + return associate('&', map(lambda c: associate('|', c), clauses)) + + +australia_sat = MapColoringSAT(list('RGB'), """SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: """) + +france_sat = MapColoringSAT(list('RGBY'), + """AL: LO FC; AQ: MP LI PC; AU: LI CE BO RA LR MP; BO: CE IF CA FC RA + AU; BR: NB PL; CA: IF PI LO FC BO; CE: PL NB NH IF BO AU LI PC; FC: BO + CA LO AL RA; IF: NH PI CA BO CE; LI: PC CE AU MP AQ; LO: CA AL FC; LR: + MP AU RA PA; MP: AQ LI AU LR; NB: NH CE PL BR; NH: PI IF CE NB; NO: + PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA: + AU BO FC PA LR""") + +usa_sat = MapColoringSAT(list('RGBY'), + """WA: OR ID; OR: ID NV CA; CA: NV AZ; NV: ID UT AZ; ID: MT WY UT; + UT: WY CO AZ; MT: ND SD WY; WY: SD NE CO; CO: NE KA OK NM; NM: OK TX AZ; + ND: MN SD; SD: MN IA NE; NE: IA MO KA; KA: MO OK; OK: MO AR TX; + TX: AR LA; MN: WI IA; IA: WI IL MO; MO: IL KY TN AR; AR: MS TN LA; + LA: MS; WI: MI IL; IL: IN KY; IN: OH KY; MS: TN AL; AL: TN GA FL; + MI: OH IN; OH: PA WV KY; KY: WV VA TN; TN: VA NC GA; GA: NC SC FL; + PA: NY NJ DE MD WV; WV: MD VA; VA: MD DC NC; NC: SC; NY: VT MA CT NJ; + NJ: DE; DE: MD; MD: DC; VT: NH MA; MA: NH RI CT; CT: RI; ME: NH; + HI: ; AK: """) + + # ______________________________________________________________________________ # Expr functions for WumpusKB and HybridWumpusAgent -def facing_east (time): +def facing_east(time): return Expr('FacingEast', time) -def facing_west (time): + +def facing_west(time): return Expr('FacingWest', time) -def facing_north (time): + +def facing_north(time): return Expr('FacingNorth', time) -def facing_south (time): + +def facing_south(time): return Expr('FacingSouth', time) -def wumpus (x, y): + +def wumpus(x, y): return Expr('W', x, y) + def pit(x, y): return Expr('P', x, y) + def breeze(x, y): return Expr('B', x, y) + def stench(x, y): return Expr('S', x, y) + def wumpus_alive(time): return Expr('WumpusAlive', time) + def have_arrow(time): return Expr('HaveArrow', time) + def percept_stench(time): return Expr('Stench', time) + def percept_breeze(time): return Expr('Breeze', time) + def percept_glitter(time): return Expr('Glitter', time) + def percept_bump(time): return Expr('Bump', time) + def percept_scream(time): return Expr('Scream', time) + def move_forward(time): return Expr('Forward', time) + def shoot(time): return Expr('Shoot', time) + def turn_left(time): return Expr('TurnLeft', time) + def turn_right(time): return Expr('TurnRight', time) + def ok_to_move(x, y, time): return Expr('OK', x, y, time) -def location(x, y, time = None): + +def location(x, y, time=None): if time is None: return Expr('L', x, y) else: return Expr('L', x, y, time) + # Symbols def implies(lhs, rhs): return Expr('==>', lhs, rhs) + def equiv(lhs, rhs): return Expr('<=>', lhs, rhs) + # Helper Function def new_disjunction(sentences): t = sentences[0] - for i in range(1,len(sentences)): + for i in range(1, len(sentences)): t |= sentences[i] return t @@ -812,62 +894,59 @@ def new_disjunction(sentences): class WumpusKB(PropKB): """ - Create a Knowledge Base that contains the atemporal "Wumpus physics" and temporal rules with time zero. + Create a Knowledge Base that contains the a temporal "Wumpus physics" and temporal rules with time zero. """ - def __init__(self,dimrow): + def __init__(self, dimrow): super().__init__() self.dimrow = dimrow - self.tell( ~wumpus(1, 1) ) - self.tell( ~pit(1, 1) ) + self.tell(~wumpus(1, 1)) + self.tell(~pit(1, 1)) - for y in range(1, dimrow+1): - for x in range(1, dimrow+1): + for y in range(1, dimrow + 1): + for x in range(1, dimrow + 1): pits_in = list() wumpus_in = list() - if x > 1: # West room exists + if x > 1: # West room exists pits_in.append(pit(x - 1, y)) wumpus_in.append(wumpus(x - 1, y)) - if y < dimrow: # North room exists + if y < dimrow: # North room exists pits_in.append(pit(x, y + 1)) wumpus_in.append(wumpus(x, y + 1)) - if x < dimrow: # East room exists + if x < dimrow: # East room exists pits_in.append(pit(x + 1, y)) wumpus_in.append(wumpus(x + 1, y)) - if y > 1: # South room exists + if y > 1: # South room exists pits_in.append(pit(x, y - 1)) wumpus_in.append(wumpus(x, y - 1)) self.tell(equiv(breeze(x, y), new_disjunction(pits_in))) self.tell(equiv(stench(x, y), new_disjunction(wumpus_in))) - - ## Rule that describes existence of at least one Wumpus + # Rule that describes existence of at least one Wumpus wumpus_at_least = list() - for x in range(1, dimrow+1): + for x in range(1, dimrow + 1): for y in range(1, dimrow + 1): wumpus_at_least.append(wumpus(x, y)) self.tell(new_disjunction(wumpus_at_least)) - - ## Rule that describes existence of at most one Wumpus - for i in range(1, dimrow+1): - for j in range(1, dimrow+1): - for u in range(1, dimrow+1): - for v in range(1, dimrow+1): - if i!=u or j!=v: + # Rule that describes existence of at most one Wumpus + for i in range(1, dimrow + 1): + for j in range(1, dimrow + 1): + for u in range(1, dimrow + 1): + for v in range(1, dimrow + 1): + if i != u or j != v: self.tell(~wumpus(i, j) | ~wumpus(u, v)) - - ## Temporal rules at time zero + # Temporal rules at time zero self.tell(location(1, 1, 0)) - for i in range(1, dimrow+1): + for i in range(1, dimrow + 1): for j in range(1, dimrow + 1): self.tell(implies(location(i, j, 0), equiv(percept_breeze(0), breeze(i, j)))) self.tell(implies(location(i, j, 0), equiv(percept_stench(0), stench(i, j)))) @@ -881,7 +960,6 @@ def __init__(self,dimrow): self.tell(~facing_south(0)) self.tell(~facing_west(0)) - def make_action_sentence(self, action, time): actions = [move_forward(time), shoot(time), turn_left(time), turn_right(time)] @@ -895,7 +973,7 @@ def make_percept_sentence(self, percept, time): # Glitter, Bump, Stench, Breeze, Scream flags = [0, 0, 0, 0, 0] - ## Things perceived + # Things perceived if isinstance(percept, Glitter): flags[0] = 1 self.tell(percept_glitter(time)) @@ -912,7 +990,7 @@ def make_percept_sentence(self, percept, time): flags[4] = 1 self.tell(percept_scream(time)) - ## Things not perceived + # Things not perceived for i in range(len(flags)): if flags[i] == 0: if i == 0: @@ -926,15 +1004,14 @@ def make_percept_sentence(self, percept, time): elif i == 4: self.tell(~percept_scream(time)) - def add_temporal_sentences(self, time): if time == 0: return t = time - 1 - ## current location rules - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + # current location rules + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): self.tell(implies(location(i, j, time), equiv(percept_breeze(time), breeze(i, j)))) self.tell(implies(location(i, j, time), equiv(percept_stench(time), stench(i, j)))) @@ -956,15 +1033,15 @@ def add_temporal_sentences(self, time): if j != self.dimrow: s.append(location(i, j + 1, t) & facing_south(t) & move_forward(t)) - ## add sentence about location i,j + # add sentence about location i,j self.tell(new_disjunction(s)) - ## add sentence about safety of location i,j + # add sentence about safety of location i,j self.tell( equiv(ok_to_move(i, j, time), ~pit(i, j) & ~wumpus(i, j) & wumpus_alive(time)) ) - ## Rules about current orientation + # Rules about current orientation a = facing_north(t) & turn_right(t) b = facing_south(t) & turn_left(t) @@ -990,16 +1067,15 @@ def add_temporal_sentences(self, time): s = equiv(facing_south(time), a | b | c) self.tell(s) - ## Rules about last action + # Rules about last action self.tell(equiv(move_forward(t), ~turn_right(t) & ~turn_left(t))) - ##Rule about the arrow + # Rule about the arrow self.tell(equiv(have_arrow(time), have_arrow(t) & ~shoot(t))) - ##Rule about Wumpus (dead or alive) + # Rule about Wumpus (dead or alive) self.tell(equiv(wumpus_alive(time), wumpus_alive(t) & ~percept_scream(time))) - def ask_if_true(self, query): return pl_resolution(self, query) @@ -1007,13 +1083,12 @@ def ask_if_true(self, query): # ______________________________________________________________________________ -class WumpusPosition(): +class WumpusPosition: def __init__(self, x, y, orientation): self.X = x self.Y = y self.orientation = orientation - def get_location(self): return self.X, self.Y @@ -1029,18 +1104,19 @@ def set_orientation(self, orientation): def __eq__(self, other): if other.get_location() == self.get_location() and \ - other.get_orientation()==self.get_orientation(): + other.get_orientation() == self.get_orientation(): return True else: return False + # ______________________________________________________________________________ class HybridWumpusAgent(Agent): """An agent for the wumpus world that does logical inference. [Figure 7.20]""" - def __init__(self,dimentions): + def __init__(self, dimentions): self.dimrow = dimentions self.kb = WumpusKB(self.dimrow) self.t = 0 @@ -1048,15 +1124,14 @@ def __init__(self,dimentions): self.current_position = WumpusPosition(1, 1, 'UP') super().__init__(self.execute) - def execute(self, percept): self.kb.make_percept_sentence(percept, self.t) self.kb.add_temporal_sentences(self.t) temp = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if self.kb.ask_if_true(location(i, j, self.t)): temp.append(i) temp.append(j) @@ -1071,8 +1146,8 @@ def execute(self, percept): self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT') safe_points = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if self.kb.ask_if_true(ok_to_move(i, j, self.t)): safe_points.append([i, j]) @@ -1080,14 +1155,14 @@ def execute(self, percept): goals = list() goals.append([1, 1]) self.plan.append('Grab') - actions = self.plan_route(self.current_position,goals,safe_points) + actions = self.plan_route(self.current_position, goals, safe_points) self.plan.extend(actions) self.plan.append('Climb') if len(self.plan) == 0: unvisited = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): for k in range(self.t): if self.kb.ask_if_true(location(i, j, k)): unvisited.append([i, j]) @@ -1097,13 +1172,13 @@ def execute(self, percept): if u not in unvisited_and_safe and s == u: unvisited_and_safe.append(u) - temp = self.plan_route(self.current_position,unvisited_and_safe,safe_points) + temp = self.plan_route(self.current_position, unvisited_and_safe, safe_points) self.plan.extend(temp) if len(self.plan) == 0 and self.kb.ask_if_true(have_arrow(self.t)): possible_wumpus = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if not self.kb.ask_if_true(wumpus(i, j)): possible_wumpus.append([i, j]) @@ -1112,8 +1187,8 @@ def execute(self, percept): if len(self.plan) == 0: not_unsafe = list() - for i in range(1, self.dimrow+1): - for j in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): + for j in range(1, self.dimrow + 1): if not self.kb.ask_if_true(ok_to_move(i, j, self.t)): not_unsafe.append([i, j]) temp = self.plan_route(self.current_position, not_unsafe, safe_points) @@ -1133,19 +1208,17 @@ def execute(self, percept): return action - def plan_route(self, current, goals, allowed): problem = PlanRoute(current, goals, allowed, self.dimrow) return astar_search(problem).solution() - def plan_shot(self, current, goals, allowed): shooting_positions = set() for loc in goals: x = loc[0] y = loc[1] - for i in range(1, self.dimrow+1): + for i in range(1, self.dimrow + 1): if i < x: shooting_positions.add(WumpusPosition(i, y, 'EAST')) if i > x: @@ -1157,7 +1230,7 @@ def plan_shot(self, current, goals, allowed): # Can't have a shooting position from any of the rooms the Wumpus could reside orientations = ['EAST', 'WEST', 'NORTH', 'SOUTH'] - for loc in goals: + for loc in goals: for orientation in orientations: shooting_positions.remove(WumpusPosition(loc[0], loc[1], orientation)) @@ -1186,7 +1259,7 @@ def translate_to_SAT(init, transition, goal, time): # Symbol claiming state s at time t state_counter = itertools.count() for s in states: - for t in range(time+1): + for t in range(time + 1): state_sym[s, t] = Expr("State_{}".format(next(state_counter))) # Add initial state axiom @@ -1206,11 +1279,11 @@ def translate_to_SAT(init, transition, goal, time): "Transition_{}".format(next(transition_counter))) # Change the state from s to s_ - clauses.append(action_sym[s, action, t] |'==>'| state_sym[s, t]) - clauses.append(action_sym[s, action, t] |'==>'| state_sym[s_, t + 1]) + clauses.append(action_sym[s, action, t] | '==>' | state_sym[s, t]) + clauses.append(action_sym[s, action, t] | '==>' | state_sym[s_, t + 1]) # Allow only one state at any time - for t in range(time+1): + for t in range(time + 1): # must be a state at any time clauses.append(associate('|', [state_sym[s, t] for s in states])) @@ -1363,6 +1436,7 @@ def standardize_variables(sentence, dic=None): standardize_variables.counter = itertools.count() + # ______________________________________________________________________________ @@ -1404,6 +1478,7 @@ def fol_fc_ask(KB, alpha): """A simple forward-chaining algorithm. [Figure 9.3]""" # TODO: Improve efficiency kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)}) + def enum_subst(p): query_vars = list({v for clause in p for v in variables(clause)}) for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)): @@ -1466,8 +1541,8 @@ def fol_bc_and(KB, goals, theta): P11, P12, P21, P22, P31, B11, B21 = expr('P11, P12, P21, P22, P31, B11, B21') wumpus_kb.tell(~P11) -wumpus_kb.tell(B11 | '<=>' | ((P12 | P21))) -wumpus_kb.tell(B21 | '<=>' | ((P11 | P22 | P31))) +wumpus_kb.tell(B11 | '<=>' | (P12 | P21)) +wumpus_kb.tell(B21 | '<=>' | (P11 | P22 | P31)) wumpus_kb.tell(~B11) wumpus_kb.tell(B21) @@ -1497,6 +1572,7 @@ def fol_bc_and(KB, goals, theta): 'Enemy(Nono, America)' ])) + # ______________________________________________________________________________ # Example application (not in the book). @@ -1527,7 +1603,7 @@ def diff(y, x): elif op == '/': return (v * diff(u, x) - u * diff(v, x)) / (v * v) elif op == '**' and isnumber(x.op): - return (v * u ** (v - 1) * diff(u, x)) + return v * u ** (v - 1) * diff(u, x) elif op == '**': return (v * u ** (v - 1) * diff(u, x) + u ** v * Expr('log')(u) * diff(v, x)) diff --git a/tests/test_csp.py b/tests/test_csp.py index c34d42540..a7564a395 100644 --- a/tests/test_csp.py +++ b/tests/test_csp.py @@ -10,16 +10,16 @@ def test_csp_assign(): var = 10 val = 5 assignment = {} - australia.assign(var, val, assignment) + australia_csp.assign(var, val, assignment) - assert australia.nassigns == 1 + assert australia_csp.nassigns == 1 assert assignment[var] == val def test_csp_unassign(): var = 10 assignment = {var: 5} - australia.unassign(var, assignment) + australia_csp.unassign(var, assignment) assert var not in assignment @@ -330,22 +330,22 @@ def test_forward_checking(): def test_backtracking_search(): - assert backtracking_search(australia) - assert backtracking_search(australia, select_unassigned_variable=mrv) - assert backtracking_search(australia, order_domain_values=lcv) - assert backtracking_search(australia, select_unassigned_variable=mrv, + assert backtracking_search(australia_csp) + assert backtracking_search(australia_csp, select_unassigned_variable=mrv) + assert backtracking_search(australia_csp, order_domain_values=lcv) + assert backtracking_search(australia_csp, select_unassigned_variable=mrv, order_domain_values=lcv) - assert backtracking_search(australia, inference=forward_checking) - assert backtracking_search(australia, inference=mac) - assert backtracking_search(usa, select_unassigned_variable=mrv, + assert backtracking_search(australia_csp, inference=forward_checking) + assert backtracking_search(australia_csp, inference=mac) + assert backtracking_search(usa_csp, select_unassigned_variable=mrv, order_domain_values=lcv, inference=mac) def test_min_conflicts(): - assert min_conflicts(australia) - assert min_conflicts(france) + assert min_conflicts(australia_csp) + assert min_conflicts(france_csp) - tests = [(usa, None)] * 3 + tests = [(usa_csp, None)] * 3 assert failure_test(min_conflicts, tests) >= 1 / 3 australia_impossible = MapColoringCSP(list('RG'), 'SA: WA NT Q NSW V; NT: WA Q; NSW: Q V; T: ') @@ -418,7 +418,7 @@ def test_parse_neighbours(): def test_topological_sort(): root = 'NT' - Sort, Parents = topological_sort(australia, root) + Sort, Parents = topological_sort(australia_csp, root) assert Sort == ['NT', 'SA', 'Q', 'NSW', 'V', 'WA'] assert Parents['NT'] == None diff --git a/tests/test_logic.py b/tests/test_logic.py index 378f1f0fc..fe9a9c5e3 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -1,10 +1,12 @@ import pytest + from logic import * -from utils import expr_handle_infix_ops, count, Symbol +from utils import expr_handle_infix_ops, count definite_clauses_KB = PropDefiniteKB() -for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', 'C']: - definite_clauses_KB.tell(expr(clause)) +for clause in ['(B & F)==>E', '(A & E & F)==>G', '(B & C)==>F', '(A & B)==>D', '(E & F)==>H', '(H & I)==>J', 'A', 'B', + 'C']: + definite_clauses_KB.tell(expr(clause)) def test_is_symbol(): @@ -47,7 +49,7 @@ def test_extend(): def test_subst(): - assert subst({x: 42, y:0}, F(x) + y) == (F(42) + 0) + assert subst({x: 42, y: 0}, F(x) + y) == (F(42) + 0) def test_PropKB(): @@ -55,7 +57,7 @@ def test_PropKB(): assert count(kb.ask(expr) for expr in [A, C, D, E, Q]) is 0 kb.tell(A & E) assert kb.ask(A) == kb.ask(E) == {} - kb.tell(E |'==>'| C) + kb.tell(E | '==>' | C) assert kb.ask(C) == {} kb.retract(E) assert kb.ask(E) is False @@ -94,14 +96,15 @@ def test_is_definite_clause(): def test_parse_definite_clause(): assert parse_definite_clause(expr('A & B & C & D ==> E')) == ([A, B, C, D], E) assert parse_definite_clause(expr('Farmer(Mac)')) == ([], expr('Farmer(Mac)')) - assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ([expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) + assert parse_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)')) == ( + [expr('Farmer(f)'), expr('Rabbit(r)')], expr('Hates(f, r)')) def test_pl_true(): assert pl_true(P, {}) is None assert pl_true(P, {P: False}) is False - assert pl_true(P | Q, {P: True}) is True - assert pl_true((A | B) & (C | D), {A: False, B: True, D: True}) is True + assert pl_true(P | Q, {P: True}) + assert pl_true((A | B) & (C | D), {A: False, B: True, D: True}) assert pl_true((A & B) & (C | D), {A: False, B: True, D: True}) is False assert pl_true((A & B) | (A & C), {A: False, B: True, C: True}) is False assert pl_true((A | B) & (C | D), {A: True, D: False}) is None @@ -131,28 +134,28 @@ def test_dpll(): assert (dpll_satisfiable(A & ~B & C & (A | ~D) & (~E | ~D) & (C | ~D) & (~A | ~F) & (E | ~F) & (~D | ~F) & (B | ~C | D) & (A | ~E | F) & (~A | E | D)) == {B: False, C: True, A: True, F: False, D: True, E: False}) - assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} - assert dpll_satisfiable((A | (B & C)) |'<=>'| ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} - assert dpll_satisfiable(A |'<=>'| B) == {A: True, B: True} + assert dpll_satisfiable(A & B & ~C & D) == {C: False, A: True, D: True, B: True} + assert dpll_satisfiable((A | (B & C)) | '<=>' | ((A | B) & (A | C))) == {C: True, A: True} or {C: True, B: True} + assert dpll_satisfiable(A | '<=>' | B) == {A: True, B: True} assert dpll_satisfiable(A & ~B) == {A: True, B: False} assert dpll_satisfiable(P & ~P) is False def test_find_pure_symbol(): - assert find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A]) == (A, True) - assert find_pure_symbol([A, B, C], [~A|~B,~B|~C,C|A]) == (B, False) - assert find_pure_symbol([A, B, C], [~A|B,~B|~C,C|A]) == (None, None) + assert find_pure_symbol([A, B, C], [A | ~B, ~B | ~C, C | A]) == (A, True) + assert find_pure_symbol([A, B, C], [~A | ~B, ~B | ~C, C | A]) == (B, False) + assert find_pure_symbol([A, B, C], [~A | B, ~B | ~C, C | A]) == (None, None) def test_unit_clause_assign(): - assert unit_clause_assign(A|B|C, {A:True}) == (None, None) - assert unit_clause_assign(B|C, {A:True}) == (None, None) - assert unit_clause_assign(B|~A, {A:True}) == (B, True) + assert unit_clause_assign(A | B | C, {A: True}) == (None, None) + assert unit_clause_assign(B | C, {A: True}) == (None, None) + assert unit_clause_assign(B | ~A, {A: True}) == (B, True) def test_find_unit_clause(): - assert find_unit_clause([A|B|C, B|~C, ~A|~B], {A:True}) == (B, False) - + assert find_unit_clause([A | B | C, B | ~C, ~A | ~B], {A: True}) == (B, False) + def test_unify(): assert unify(x, x, {}) == {} @@ -175,9 +178,9 @@ def test_tt_entails(): assert tt_entails(P & Q, Q) assert not tt_entails(P | Q, Q) assert tt_entails(A & (B | C) & E & F & ~(P | Q), A & E & F & ~P & ~Q) - assert not tt_entails(P |'<=>'| Q, Q) - assert tt_entails((P |'==>'| Q) & P, Q) - assert not tt_entails((P |'<=>'| Q) & ~P, Q) + assert not tt_entails(P | '<=>' | Q, Q) + assert tt_entails((P | '==>' | Q) & P, Q) + assert not tt_entails((P | '<=>' | Q) & ~P, Q) def test_prop_symbols(): @@ -231,12 +234,13 @@ def test_move_not_inwards(): def test_distribute_and_over_or(): - def test_entailment(s, has_and = False): + def test_entailment(s, has_and=False): result = distribute_and_over_or(s) if has_and: assert result.op == '&' assert tt_entails(s, result) assert tt_entails(result, s) + test_entailment((A & B) | C, True) test_entailment((A | B) & C, True) test_entailment((A | B) | C, False) @@ -253,7 +257,8 @@ def test_to_cnf(): assert repr(to_cnf("a | (b & c) | d")) == '((b | a | d) & (c | a | d))' assert repr(to_cnf("A & (B | (D & E))")) == '(A & (D | B) & (E | B))' assert repr(to_cnf("A | (B | (C | (D & E)))")) == '((D | A | B | C) & (E | A | B | C))' - assert repr(to_cnf('(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' + assert repr(to_cnf( + '(A <=> ~B) ==> (C | ~D)')) == '((B | ~A | C | ~D) & (A | ~A | C | ~D) & (B | ~B | C | ~D) & (A | ~B | C | ~D))' def test_pl_resolution(): @@ -281,6 +286,7 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) + assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]' @@ -295,6 +301,7 @@ def test_ask(query, kb=None): return sorted( [dict((x, v) for x, v in list(a.items()) if x in test_variables) for a in answers], key=repr) + assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' assert repr(test_ask('Enemy(x, America)', crime_kb)) == '[{x: Nono}]' assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' @@ -316,6 +323,7 @@ def check_SAT(clauses, single_solution={}): if single_solution: # Cross check the solution if only one exists assert all(pl_true(x, single_solution) for x in clauses) assert soln == single_solution + # Test WalkSat for problems with solution check_SAT([A & B, A & C]) check_SAT([A | B, P & Q, P & B]) From aaea704ac786072702e0280155068cee759734eb Mon Sep 17 00:00:00 2001 From: Donato Meoli Date: Mon, 29 Jul 2019 14:50:19 +0200 Subject: [PATCH 20/21] removed redundant parentheses --- csp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csp.py b/csp.py index 4c1203f4a..e1ee53a89 100644 --- a/csp.py +++ b/csp.py @@ -365,7 +365,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents): visited[node] = True for n in neighbors[node]: - if (not visited[n]): + if not visited[n]: build_topological(n, node, neighbors, visited, stack, parents) parents[node] = parent From be656aa86415a9e4f7f8e3668d1f092f7ae826ba Mon Sep 17 00:00:00 2001 From: DonatoMeoli Date: Wed, 14 Aug 2019 13:53:06 +0200 Subject: [PATCH 21/21] added Viterbi algorithm --- probability.py | 56 +++++++++++-- tests/test_probability.py | 160 +++++++++++++++++++++----------------- 2 files changed, 139 insertions(+), 77 deletions(-) diff --git a/probability.py b/probability.py index 458273b92..c907e348d 100644 --- a/probability.py +++ b/probability.py @@ -13,19 +13,23 @@ from collections import defaultdict from functools import reduce + # ______________________________________________________________________________ def DTAgentProgram(belief_state): """A decision-theoretic agent. [Figure 13.1]""" + def program(percept): belief_state.observe(program.action, percept) program.action = argmax(belief_state.actions(), key=belief_state.expected_outcome_utility) return program.action + program.action = None return program + # ______________________________________________________________________________ @@ -132,6 +136,7 @@ def event_values(event, variables): else: return tuple([event[var] for var in variables]) + # ______________________________________________________________________________ @@ -160,6 +165,7 @@ def enumerate_joint(variables, e, P): return sum([enumerate_joint(rest, extend(e, Y, y), P) for y in P.values(Y)]) + # ______________________________________________________________________________ @@ -378,6 +384,7 @@ def __repr__(self): ('MaryCalls', 'Alarm', {T: 0.70, F: 0.01}) ]) + # ______________________________________________________________________________ @@ -409,6 +416,7 @@ def enumerate_all(variables, e, bn): return sum(Ynode.p(y, e) * enumerate_all(rest, extend(e, Y, y), bn) for y in bn.variable_values(Y)) + # ______________________________________________________________________________ @@ -498,6 +506,7 @@ def all_events(variables, bn, e): for x in bn.variable_values(X): yield extend(e1, X, x) + # ______________________________________________________________________________ # [Figure 14.12a]: sprinkler network @@ -510,6 +519,7 @@ def all_events(variables, bn, e): ('WetGrass', 'Sprinkler Rain', {(T, T): 0.99, (T, F): 0.90, (F, T): 0.90, (F, F): 0.00})]) + # ______________________________________________________________________________ @@ -521,6 +531,7 @@ def prior_sample(bn): event[node.variable] = node.sample(event) return event + # _________________________________________________________________________ @@ -547,6 +558,7 @@ def consistent_with(event, evidence): return all(evidence.get(k, v) == v for k, v in event.items()) + # _________________________________________________________________________ @@ -579,6 +591,7 @@ def weighted_sample(bn, e): event[Xi] = node.sample(event) return event, w + # _________________________________________________________________________ @@ -612,6 +625,7 @@ def markov_blanket_sample(X, e, bn): # (assuming a Boolean variable here) return probability(Q.normalize()[True]) + # _________________________________________________________________________ @@ -655,7 +669,7 @@ def forward_backward(HMM, ev, prior): fv = [[0.0, 0.0] for _ in range(len(ev))] b = [1.0, 1.0] - bv = [b] # we don't need bv; but we will have a list of all backward messages here + bv = [b] # we don't need bv; but we will have a list of all backward messages here sv = [[0, 0] for _ in range(len(ev))] fv[0] = prior @@ -671,6 +685,33 @@ def forward_backward(HMM, ev, prior): return sv + +def viterbi(HMM, ev, prior): + """[Figure 15.5] + Viterbi algorithm to find the most likely sequence. Computes the best path, + given an HMM model and a sequence of observations.""" + t = len(ev) + ev.insert(0, None) + + m = [[0.0, 0.0] for _ in range(len(ev) - 1)] + + # the recursion is initialized with m1 = forward(P(X0), e1) + m[0] = forward(HMM, prior, ev[1]) + + for i in range(1, t): + m[i] = element_wise_product(HMM.sensor_dist(ev[i + 1]), + [max(element_wise_product(HMM.transition_model[0], m[i - 1])), + max(element_wise_product(HMM.transition_model[1], m[i - 1]))]) + + path = [0.0] * (len(ev) - 1) + # the construction of the most likely sequence starts in the final state with the largest probability, + # and runs backwards; the algorithm needs to store for each xt its best predecessor xt-1 + for i in range(t, -1, -1): + path[i - 1] = max(m[i - 1]) + + return path + + # _________________________________________________________________________ @@ -702,6 +743,7 @@ def fixed_lag_smoothing(e_t, HMM, d, ev, t): else: return None + # _________________________________________________________________________ @@ -742,13 +784,15 @@ def particle_filtering(e, N, HMM): return s + # _________________________________________________________________________ -## TODO: Implement continuous map for MonteCarlo similar to Fig25.10 from the book +# TODO: Implement continuous map for MonteCarlo similar to Fig25.10 from the book class MCLmap: """Map which provides probability distributions and sensor readings. Consists of discrete cells which are either an obstacle or empty""" + def __init__(self, m): self.m = m self.nrows = len(m) @@ -772,7 +816,7 @@ def ray_cast(self, sensor_num, kin_state): # 0 # 3R1 # 2 - delta = ((sensor_num % 2 == 0)*(sensor_num - 1), (sensor_num % 2 == 1)*(2 - sensor_num)) + delta = ((sensor_num % 2 == 0) * (sensor_num - 1), (sensor_num % 2 == 1) * (2 - sensor_num)) # sensor direction changes based on orientation for _ in range(orient): delta = (delta[1], -delta[0]) @@ -790,9 +834,9 @@ def ray_cast(sensor_num, kin_state, m): return m.ray_cast(sensor_num, kin_state) M = len(z) - W = [0]*N - S_ = [0]*N - W_ = [0]*N + W = [0] * N + S_ = [0] * N + W_ = [0] * N v = a['v'] w = a['w'] diff --git a/tests/test_probability.py b/tests/test_probability.py index b4d720937..e4a83ae47 100644 --- a/tests/test_probability.py +++ b/tests/test_probability.py @@ -1,4 +1,7 @@ import random + +import pytest + from probability import * from utils import rounder @@ -47,7 +50,7 @@ def test_probdist_frequency(): P = ProbDist('Pascal-5', {'x1': 1, 'x2': 5, 'x3': 10, 'x4': 10, 'x5': 5, 'x6': 1}) assert (P['x1'], P['x2'], P['x3'], P['x4'], P['x5'], P['x6']) == ( - 0.03125, 0.15625, 0.3125, 0.3125, 0.15625, 0.03125) + 0.03125, 0.15625, 0.3125, 0.3125, 0.15625, 0.03125) def test_probdist_normalize(): @@ -60,7 +63,7 @@ def test_probdist_normalize(): P['1'], P['2'], P['3'], P['4'], P['5'], P['6'] = 10, 15, 25, 30, 40, 80 P = P.normalize() assert (P.prob['1'], P.prob['2'], P.prob['3'], P.prob['4'], P.prob['5'], P.prob['6']) == ( - 0.05, 0.075, 0.125, 0.15, 0.2, 0.4) + 0.05, 0.075, 0.125, 0.15, 0.2, 0.4) def test_jointprob(): @@ -106,7 +109,7 @@ def test_enumerate_joint_ask(): P[0, 1] = 0.5 P[1, 1] = P[2, 1] = 0.125 assert enumerate_joint_ask( - 'X', dict(Y=1), P).show_approx() == '0: 0.667, 1: 0.167, 2: 0.167' + 'X', dict(Y=1), P).show_approx() == '0: 0.667, 1: 0.167, 2: 0.167' def test_bayesnode_p(): @@ -126,38 +129,38 @@ def test_bayesnode_sample(): def test_enumeration_ask(): assert enumeration_ask( - 'Burglary', dict(JohnCalls=T, MaryCalls=T), - burglary).show_approx() == 'False: 0.716, True: 0.284' + 'Burglary', dict(JohnCalls=T, MaryCalls=T), + burglary).show_approx() == 'False: 0.716, True: 0.284' assert enumeration_ask( - 'Burglary', dict(JohnCalls=T, MaryCalls=F), - burglary).show_approx() == 'False: 0.995, True: 0.00513' + 'Burglary', dict(JohnCalls=T, MaryCalls=F), + burglary).show_approx() == 'False: 0.995, True: 0.00513' assert enumeration_ask( - 'Burglary', dict(JohnCalls=F, MaryCalls=T), - burglary).show_approx() == 'False: 0.993, True: 0.00688' + 'Burglary', dict(JohnCalls=F, MaryCalls=T), + burglary).show_approx() == 'False: 0.993, True: 0.00688' assert enumeration_ask( - 'Burglary', dict(JohnCalls=T), - burglary).show_approx() == 'False: 0.984, True: 0.0163' + 'Burglary', dict(JohnCalls=T), + burglary).show_approx() == 'False: 0.984, True: 0.0163' assert enumeration_ask( - 'Burglary', dict(MaryCalls=T), - burglary).show_approx() == 'False: 0.944, True: 0.0561' + 'Burglary', dict(MaryCalls=T), + burglary).show_approx() == 'False: 0.944, True: 0.0561' def test_elemination_ask(): assert elimination_ask( - 'Burglary', dict(JohnCalls=T, MaryCalls=T), - burglary).show_approx() == 'False: 0.716, True: 0.284' + 'Burglary', dict(JohnCalls=T, MaryCalls=T), + burglary).show_approx() == 'False: 0.716, True: 0.284' assert elimination_ask( - 'Burglary', dict(JohnCalls=T, MaryCalls=F), - burglary).show_approx() == 'False: 0.995, True: 0.00513' + 'Burglary', dict(JohnCalls=T, MaryCalls=F), + burglary).show_approx() == 'False: 0.995, True: 0.00513' assert elimination_ask( - 'Burglary', dict(JohnCalls=F, MaryCalls=T), - burglary).show_approx() == 'False: 0.993, True: 0.00688' + 'Burglary', dict(JohnCalls=F, MaryCalls=T), + burglary).show_approx() == 'False: 0.993, True: 0.00688' assert elimination_ask( - 'Burglary', dict(JohnCalls=T), - burglary).show_approx() == 'False: 0.984, True: 0.0163' + 'Burglary', dict(JohnCalls=T), + burglary).show_approx() == 'False: 0.984, True: 0.0163' assert elimination_ask( - 'Burglary', dict(MaryCalls=T), - burglary).show_approx() == 'False: 0.944, True: 0.0561' + 'Burglary', dict(MaryCalls=T), + burglary).show_approx() == 'False: 0.944, True: 0.0561' def test_prior_sample(): @@ -189,80 +192,80 @@ def test_prior_sample2(): def test_rejection_sampling(): random.seed(47) assert rejection_sampling( - 'Burglary', dict(JohnCalls=T, MaryCalls=T), - burglary, 10000).show_approx() == 'False: 0.7, True: 0.3' + 'Burglary', dict(JohnCalls=T, MaryCalls=T), + burglary, 10000).show_approx() == 'False: 0.7, True: 0.3' assert rejection_sampling( - 'Burglary', dict(JohnCalls=T, MaryCalls=F), - burglary, 10000).show_approx() == 'False: 1, True: 0' + 'Burglary', dict(JohnCalls=T, MaryCalls=F), + burglary, 10000).show_approx() == 'False: 1, True: 0' assert rejection_sampling( - 'Burglary', dict(JohnCalls=F, MaryCalls=T), - burglary, 10000).show_approx() == 'False: 0.987, True: 0.0128' + 'Burglary', dict(JohnCalls=F, MaryCalls=T), + burglary, 10000).show_approx() == 'False: 0.987, True: 0.0128' assert rejection_sampling( - 'Burglary', dict(JohnCalls=T), - burglary, 10000).show_approx() == 'False: 0.982, True: 0.0183' + 'Burglary', dict(JohnCalls=T), + burglary, 10000).show_approx() == 'False: 0.982, True: 0.0183' assert rejection_sampling( - 'Burglary', dict(MaryCalls=T), - burglary, 10000).show_approx() == 'False: 0.965, True: 0.0348' + 'Burglary', dict(MaryCalls=T), + burglary, 10000).show_approx() == 'False: 0.965, True: 0.0348' def test_rejection_sampling2(): random.seed(42) assert rejection_sampling( - 'Cloudy', dict(Rain=T, Sprinkler=T), - sprinkler, 10000).show_approx() == 'False: 0.56, True: 0.44' + 'Cloudy', dict(Rain=T, Sprinkler=T), + sprinkler, 10000).show_approx() == 'False: 0.56, True: 0.44' assert rejection_sampling( - 'Cloudy', dict(Rain=T, Sprinkler=F), - sprinkler, 10000).show_approx() == 'False: 0.119, True: 0.881' + 'Cloudy', dict(Rain=T, Sprinkler=F), + sprinkler, 10000).show_approx() == 'False: 0.119, True: 0.881' assert rejection_sampling( - 'Cloudy', dict(Rain=F, Sprinkler=T), - sprinkler, 10000).show_approx() == 'False: 0.951, True: 0.049' + 'Cloudy', dict(Rain=F, Sprinkler=T), + sprinkler, 10000).show_approx() == 'False: 0.951, True: 0.049' assert rejection_sampling( - 'Cloudy', dict(Rain=T), - sprinkler, 10000).show_approx() == 'False: 0.205, True: 0.795' + 'Cloudy', dict(Rain=T), + sprinkler, 10000).show_approx() == 'False: 0.205, True: 0.795' assert rejection_sampling( - 'Cloudy', dict(Sprinkler=T), - sprinkler, 10000).show_approx() == 'False: 0.835, True: 0.165' + 'Cloudy', dict(Sprinkler=T), + sprinkler, 10000).show_approx() == 'False: 0.835, True: 0.165' def test_likelihood_weighting(): random.seed(1017) assert likelihood_weighting( - 'Burglary', dict(JohnCalls=T, MaryCalls=T), - burglary, 10000).show_approx() == 'False: 0.702, True: 0.298' + 'Burglary', dict(JohnCalls=T, MaryCalls=T), + burglary, 10000).show_approx() == 'False: 0.702, True: 0.298' assert likelihood_weighting( - 'Burglary', dict(JohnCalls=T, MaryCalls=F), - burglary, 10000).show_approx() == 'False: 0.993, True: 0.00656' + 'Burglary', dict(JohnCalls=T, MaryCalls=F), + burglary, 10000).show_approx() == 'False: 0.993, True: 0.00656' assert likelihood_weighting( - 'Burglary', dict(JohnCalls=F, MaryCalls=T), - burglary, 10000).show_approx() == 'False: 0.996, True: 0.00363' + 'Burglary', dict(JohnCalls=F, MaryCalls=T), + burglary, 10000).show_approx() == 'False: 0.996, True: 0.00363' assert likelihood_weighting( - 'Burglary', dict(JohnCalls=F, MaryCalls=F), - burglary, 10000).show_approx() == 'False: 1, True: 0.000126' + 'Burglary', dict(JohnCalls=F, MaryCalls=F), + burglary, 10000).show_approx() == 'False: 1, True: 0.000126' assert likelihood_weighting( - 'Burglary', dict(JohnCalls=T), - burglary, 10000).show_approx() == 'False: 0.979, True: 0.0205' + 'Burglary', dict(JohnCalls=T), + burglary, 10000).show_approx() == 'False: 0.979, True: 0.0205' assert likelihood_weighting( - 'Burglary', dict(MaryCalls=T), - burglary, 10000).show_approx() == 'False: 0.94, True: 0.0601' + 'Burglary', dict(MaryCalls=T), + burglary, 10000).show_approx() == 'False: 0.94, True: 0.0601' def test_likelihood_weighting2(): random.seed(42) assert likelihood_weighting( - 'Cloudy', dict(Rain=T, Sprinkler=T), - sprinkler, 10000).show_approx() == 'False: 0.559, True: 0.441' + 'Cloudy', dict(Rain=T, Sprinkler=T), + sprinkler, 10000).show_approx() == 'False: 0.559, True: 0.441' assert likelihood_weighting( - 'Cloudy', dict(Rain=T, Sprinkler=F), - sprinkler, 10000).show_approx() == 'False: 0.12, True: 0.88' + 'Cloudy', dict(Rain=T, Sprinkler=F), + sprinkler, 10000).show_approx() == 'False: 0.12, True: 0.88' assert likelihood_weighting( - 'Cloudy', dict(Rain=F, Sprinkler=T), - sprinkler, 10000).show_approx() == 'False: 0.951, True: 0.0486' + 'Cloudy', dict(Rain=F, Sprinkler=T), + sprinkler, 10000).show_approx() == 'False: 0.951, True: 0.0486' assert likelihood_weighting( - 'Cloudy', dict(Rain=T), - sprinkler, 10000).show_approx() == 'False: 0.198, True: 0.802' + 'Cloudy', dict(Rain=T), + sprinkler, 10000).show_approx() == 'False: 0.198, True: 0.802' assert likelihood_weighting( - 'Cloudy', dict(Sprinkler=T), - sprinkler, 10000).show_approx() == 'False: 0.833, True: 0.167' + 'Cloudy', dict(Sprinkler=T), + sprinkler, 10000).show_approx() == 'False: 0.833, True: 0.167' def test_forward_backward(): @@ -278,8 +281,23 @@ def test_forward_backward(): umbrella_evidence = [T, F, T, F, T] assert rounder(forward_backward(umbrellaHMM, umbrella_evidence, umbrella_prior)) == [ - [0.5871, 0.4129], [0.7177, 0.2823], [0.2324, 0.7676], [0.6072, 0.3928], - [0.2324, 0.7676], [0.7177, 0.2823]] + [0.5871, 0.4129], [0.7177, 0.2823], [0.2324, 0.7676], [0.6072, 0.3928], + [0.2324, 0.7676], [0.7177, 0.2823]] + + +def test_viterbi(): + umbrella_prior = [0.5, 0.5] + umbrella_transition = [[0.7, 0.3], [0.3, 0.7]] + umbrella_sensor = [[0.9, 0.2], [0.1, 0.8]] + umbrellaHMM = HiddenMarkovModel(umbrella_transition, umbrella_sensor) + + umbrella_evidence = [T, T, F, T, T] + assert (rounder(viterbi(umbrellaHMM, umbrella_evidence, umbrella_prior)) == + [0.8182, 0.5155, 0.1237, 0.0334, 0.0210]) + + umbrella_evidence = [T, F, T, F, T] + assert (rounder(viterbi(umbrellaHMM, umbrella_evidence, umbrella_prior)) == + [0.8182, 0.1964, 0.053, 0.0154, 0.0042]) def test_fixed_lag_smoothing(): @@ -318,7 +336,7 @@ def test_particle_filtering(): def test_monte_carlo_localization(): - ## TODO: Add tests for random motion/inaccurate sensors + # TODO: Add tests for random motion/inaccurate sensors random.seed('aima-python') m = MCLmap([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0], @@ -339,7 +357,7 @@ def P_motion_sample(kin_state, v, w): orient = kin_state[2] # for simplicity the robot first rotates and then moves - orient = (orient + w)%4 + orient = (orient + w) % 4 for _ in range(orient): v = (v[1], -v[0]) pos = vector_add(pos, v) @@ -359,7 +377,7 @@ def P_sensor(x, y): a = {'v': (0, 0), 'w': 0} z = (2, 4, 1, 6) S = monte_carlo_localization(a, z, 1000, P_motion_sample, P_sensor, m) - grid = [[0]*17 for _ in range(11)] + grid = [[0] * 17 for _ in range(11)] for x, y, _ in S: if 0 <= x < 11 and 0 <= y < 17: grid[x][y] += 1 @@ -369,7 +387,7 @@ def P_sensor(x, y): a = {'v': (0, 1), 'w': 0} z = (2, 3, 5, 7) S = monte_carlo_localization(a, z, 1000, P_motion_sample, P_sensor, m, S) - grid = [[0]*17 for _ in range(11)] + grid = [[0] * 17 for _ in range(11)] for x, y, _ in S: if 0 <= x < 11 and 0 <= y < 17: grid[x][y] += 1