Backend Logic
Python Implementation
The core AI engine — Knowledge Base, CNF conversion, and Resolution Refutation — implemented in Python.
knowledge_base.py
resolution.py
agent.py
world.py
# ── Knowledge Base — Propositional Logic ──────────────────────────────
class KnowledgeBase:
def __init__(self):
self.clauses = [] # CNF clauses (list of frozensets)
self.facts = {} # ground truth facts
self.safe = set() # KB-proven safe cells
self.unsafe = set() # KB-proven unsafe cells
def tell(self, literal):
"""Add a unit clause (literal) to the KB."""
clause = frozenset([literal])
if clause not in self.clauses:
self.clauses.append(clause)
self.facts[literal] = True
def tell_clause(self, *literals):
"""Add a disjunctive clause to the KB."""
clause = frozenset(literals)
if clause not in self.clauses:
self.clauses.append(clause)
def add_breeze_axioms(self, r, c, N, breeze):
"""Encode Breeze(r,c) ↔ ∃adjacent Pit axioms in CNF."""
adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)]
adj = [(x,y) for x,y in adj if 0<=xand 0<=yif not breeze:
for (x,y) in adj:
self.tell(f"¬P_{x}_{y}") # no pit in any adjacent cell
else:
# Breeze → at least one adjacent pit (disjunction)
self.tell_clause(*[f"P_{x}_{y}" for (x,y) in adj])
def add_stench_axioms(self, r, c, N, stench):
"""Encode Stench(r,c) ↔ ∃adjacent Wumpus axioms."""
adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)]
adj = [(x,y) for x,y in adj if 0<=xand 0<=yif not stench:
for (x,y) in adj:
self.tell(f"¬W_{x}_{y}")
else:
self.tell_clause(*[f"W_{x}_{y}" for (x,y) in adj])
# ── Resolution Refutation Engine ──────────────────────────────────────
def resolve(ci, cj):
"""Resolve two clauses; return set of resolvents or None."""
resolvents = set()
for literal in ci:
neg = literal[1:] if literal.startswith('¬') else '¬' + literal
if neg in cj:
new_clause = (ci | cj) - {literal, neg}
resolvents.add(frozenset(new_clause))
return resolvents
def resolution_refutation(kb_clauses, query):
"""
Prove query by contradiction.
Add ¬query to KB and resolve until ∅ (contradiction).
Returns (True, steps) if query is entailed, else (False, steps).
"""
neg_query = query[1:] if query.startswith('¬') else '¬' + query
clauses = [frozenset(c) for c in kb_clauses]
clauses.append(frozenset([neg_query]))
steps = [f"Added ¬query: {{{neg_query}}}"]
new = set()
while True:
pairs = [(clauses[i], clauses[j])
for i in range(len(clauses))
for j in range(i+1, len(clauses))]
for (ci, cj) in pairs:
resolvents = resolve(ci, cj)
if frozenset() in resolvents:
steps.append("⊥ Contradiction found → query PROVED")
return True, steps
new |= resolvents
if new.issubset(set(clauses)):
steps.append("No contradiction → query UNPROVABLE")
return False, steps
for c in new:
if c not in clauses:
clauses.append(c)
steps.append(f"New resolvent: {set(c)}")
# ── Knowledge-Based Wumpus Agent ──────────────────────────────────────
class WumpusAgent:
def __init__(self, world):
self.world = world
self.kb = KnowledgeBase()
self.pos = (0, 0)
self.visited = {(0, 0)}
self.safe_q = [] # queue of KB-proven safe cells
self.alive = True
self.gold = False
self.steps = 0
self.inferences = 0
def perceive(self):
"""Sense environment and update KB."""
r, c = self.pos
percepts = self.world.get_percepts(r, c)
# Add visited cell as safe
self.kb.tell(f"Safe_{r}_{c}")
self.kb.tell(f"¬P_{r}_{c}")
self.kb.tell(f"¬W_{r}_{c}")
# Encode percept axioms
self.kb.add_breeze_axioms(r, c, self.world.N, 'Breeze' in percepts)
self.kb.add_stench_axioms(r, c, self.world.N, 'Stench' in percepts)
return percepts
def ask_safe(self, r, c):
"""Use Resolution Refutation to check if (r,c) is safe."""
proved, steps = resolution_refutation(
self.kb.clauses, f"Safe_{r}_{c}")
self.inferences += len(steps)
if proved:
self.kb.safe.add((r,c))
return proved, steps
def step(self):
"""Execute one agent step: perceive → reason → act."""
percepts = self.perceive()
r, c = self.pos
adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)]
adj = [(x,y) for x,y in adj
if 0<=xand 0<=yand (x,y) not in self.visited]
# Ask KB about each adjacent unvisited cell
safe_moves = []
for cell in adj:
proved, _ = self.ask_safe(*cell)
if proved:
safe_moves.append(cell)
if safe_moves:
self.pos = safe_moves[0]
elif adj:
self.pos = adj[0] # cautious frontier move
else:
return "STUCK"
self.visited.add(self.pos)
self.steps += 1
# Check for hazards
if self.world.has_wumpus(*self.pos) or self.world.has_pit(*self.pos):
self.alive = False
return "DEAD"
if self.world.has_gold(*self.pos):
self.gold = True
return "GOLD"
return "OK"
# ── Dynamic Wumpus World Generator ────────────────────────────────────
import random
class WumpusWorld:
def __init__(self, N=6, pit_prob=0.15):
self.N = N
self.pit_prob = pit_prob
self.pits = set()
self.wumpus = None
self.gold = None
self._generate()
def _generate(self):
"""Randomly place pits, Wumpus, and gold (not at start)."""
cells = [(r,c) for r in range(self.N)
for c in range(self.N)
if (r,c) != (0,0)]
for cell in cells:
if random.random() < self.pit_prob:
self.pits.add(cell)
candidates = [c for c in cells if c not in self.pits]
self.wumpus = random.choice(candidates)
candidates.remove(self.wumpus)
self.gold = random.choice(candidates)
def get_percepts(self, r, c):
"""Return list of active percepts at (r,c)."""
percepts = []
adj = [(r+1,c),(r-1,c),(r,c+1),(r,c-1)]
adj = [(x,y) for x,y in adj if 0<=xand 0<=yif any(cell in self.pits for cell in adj):
percepts.append('Breeze')
if any(cell == self.wumpus for cell in adj):
percepts.append('Stench')
if (r,c) == self.gold:
percepts.append('Glitter')
return percepts
has_pit = lambda self,r,c: (r,c) in self.pits
has_wumpus = lambda self,r,c: (r,c) == self.wumpus
has_gold = lambda self,r,c: (r,c) == self.gold