diff --git a/csp.py b/csp.py index ee59d4a6b..e1ee53a89 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])