Files
KI/P3/Task/Propositions.py
2025-06-28 20:56:09 +02:00

284 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from collections import deque
import re
class HornKnowledgebasedAgent:
"""Knowledge-based agent using Horn Clauses + Forward Chaining"""
def __init__(self):
"""Initialize the agent"""
self.knowledge_base = []
# ==================== MAIN INTERFACE ====================
def TELL(self, input_data):
"""Add sensor data from gym step result or legacy string
Args:
input_data: Either string ("S11") or gym step result tuple
"""
# Handle legacy string input
if isinstance(input_data, str):
self._add_to_kb(input_data)
self._auto_generate_rules(input_data)
return
# Handle gym environment step result
if isinstance(input_data, tuple) and len(input_data) >= 3:
observation, reward, terminated = input_data[0], input_data[1], input_data[2]
# Extract position (convert 0-based to 1-based)
x = observation['x'] + 1
y = observation['y'] + 1
position = f"{x}{y}"
print(f"\n🎮 Processing step for position [{x},{y}] - Terminated: {terminated}")
# Handle death vs survival
if terminated:
# Agent died - add both Wumpus AND Pit facts (Horn approximation)
self._add_to_kb(f"W{position}")
self._add_to_kb(f"P{position}")
print(f"💀 Death: W{position} ∧ P{position} (Horn approx. of W{position} P{position})")
return
else:
# Agent survived - field is safe
self._add_to_kb(f"-W{position}")
self._add_to_kb(f"-P{position}")
print(f"✅ Survival: ¬W{position} ∧ ¬P{position}")
# Process sensors
sensors = {
'stench': 'S', 'breeze': 'B', 'glitter': 'G',
'bump': 'BUMP', 'scream': 'SC'
}
for sensor_key, sensor_code in sensors.items():
sensor_value = observation.get(sensor_key, False)
if sensor_value:
sensor_fact = f"{sensor_code}{position}"
else:
sensor_fact = f"-{sensor_code}{position}"
print(f" Sensor: {sensor_fact}")
self._add_to_kb(sensor_fact)
self._auto_generate_rules(sensor_fact)
def ASK(self, query):
"""Check if KB entails query using Forward Chaining"""
result = self._forward_chaining(query)
print(f"ASK({query}): {result}")
return result
def _add_to_kb(self, sentence):
"""Add sentence to KB if not already present"""
if sentence not in self.knowledge_base:
self.knowledge_base.append(sentence)
print(f" Added: {sentence}")
# ==================== FORWARD CHAINING ====================
def _forward_chaining(self, query):
"""Forward Chaining algorithm"""
facts, rules = self._parse_kb()
# Count premises for each rule
count = {i: len(rule['premises']) for i, rule in enumerate(rules)}
# Track inferred symbols
inferred = {}
derived_facts = set(facts)
agenda = deque(facts)
while agenda:
fact = agenda.popleft()
# Check for contradiction
negated = self._negate(fact)
if negated in derived_facts:
print(f"⚠️ Contradiction: {fact} and {negated}")
return False
# Found query?
if fact == query:
return True
# Process rules
if not inferred.get(fact, False):
inferred[fact] = True
for i, rule in enumerate(rules):
if fact in rule['premises']:
count[i] -= 1
if count[i] == 0:
conclusion = rule['conclusion']
if not inferred.get(conclusion, False):
agenda.append(conclusion)
derived_facts.add(conclusion)
# Check if negation of query was derived
negated_query = self._negate(query)
if negated_query in derived_facts:
print(f" {query} is FALSE (KB entails {negated_query})")
return False
print(f" {query} cannot be proven")
return False
def _parse_kb(self):
"""Parse KB into facts and rules"""
facts = []
rules = []
for sentence in self.knowledge_base:
if '=>' in sentence:
premises_part, conclusion = sentence.split('=>')
premises = [p.strip() for p in premises_part.split(',')]
rules.append({'premises': premises, 'conclusion': conclusion.strip()})
else:
facts.append(sentence)
return facts, rules
def _negate(self, literal):
"""Negate a literal"""
return literal[1:] if literal.startswith('-') else f"-{literal}"
# ==================== RULE GENERATION ====================
def _auto_generate_rules(self, sensor):
"""Generate Horn rules from sensor facts"""
# STENCH rules
if re.match(r'^S(\d)(\d)$', sensor):
x, y = int(sensor[1]), int(sensor[2])
pos = f"{x}{y}"
# S11 => -W11 (no Wumpus in same field)
self._add_to_kb(f"S{pos}=>-W{pos}")
# S11 => W21, S11 => W12 (direct rules for neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"S{pos}=>W{nx}{ny}")
print(f" Generated stench rules for S{pos}")
# NO STENCH rules
elif re.match(r'^-S(\d)(\d)$', sensor):
x, y = int(sensor[2]), int(sensor[3])
pos = f"{x}{y}"
# -S12 => -W11, -S12 => -W22 (no Wumpus in neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"-S{pos}=>-W{nx}{ny}")
print(f" Generated no-stench rules for -S{pos}")
# BREEZE rules
elif re.match(r'^B(\d)(\d)$', sensor):
x, y = int(sensor[1]), int(sensor[2])
pos = f"{x}{y}"
# B11 => -P11 (no Pit in same field)
self._add_to_kb(f"B{pos}=>-P{pos}")
# B11 => P21, B11 => P12 (direct rules for neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"B{pos}=>P{nx}{ny}")
print(f" Generated breeze rules for B{pos}")
# NO BREEZE rules
elif re.match(r'^-B(\d)(\d)$', sensor):
x, y = int(sensor[2]), int(sensor[3])
pos = f"{x}{y}"
# -B12 => -P11, -B12 => -P22 (no Pits in neighbors)
for nx, ny in self._get_neighbors(x, y):
self._add_to_kb(f"-B{pos}=>-P{nx}{ny}")
print(f" Generated no-breeze rules for -B{pos}")
# GLITTER rules
elif re.match(r'^G(\d)(\d)$', sensor):
x, y = int(sensor[1]), int(sensor[2])
self._add_to_kb(f"G{x}{y}=>Gold{x}{y}")
print(f" Generated glitter rule")
elif re.match(r'^-G(\d)(\d)$', sensor):
x, y = int(sensor[2]), int(sensor[3])
self._add_to_kb(f"-G{x}{y}=>-Gold{x}{y}")
print(f" Generated no-glitter rule")
# SCREAM rules
elif sensor == "SC":
self._add_to_kb("SC=>WumpusDead")
print(f" Generated scream rule")
elif sensor == "-SC":
self._add_to_kb("-SC=>WumpusAlive")
print(f" Generated no-scream rule")
def _get_neighbors(self, x, y):
"""Get valid neighbors in 4x4 grid"""
neighbors = []
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = x + dx, y + dy
if 1 <= nx <= 4 and 1 <= ny <= 4:
neighbors.append((nx, ny))
return neighbors
def print_kb(self):
"""Print knowledge base"""
print("\n=== Horn Clauses Knowledge Base ===")
facts = [s for s in self.knowledge_base if '=>' not in s]
rules = [s for s in self.knowledge_base if '=>' in s]
print(f"Facts ({len(facts)}):")
for i, fact in enumerate(facts, 1):
print(f" {i}. {fact}")
print(f"Rules ({len(rules)}):")
for i, rule in enumerate(rules, 1):
print(f" {i}. {rule}")
# ==================== TESTS ====================
def test_horn_gym_integration():
"""Test Horn agent with gym integration"""
print("🎯 Horn Clauses Agent - Gym Integration Test")
print("="*50)
agent = HornKnowledgebasedAgent()
# Agent survives at [1,1] with stench
step_result_1 = (
{'x': 0, 'y': 0, 'stench': True, 'breeze': False, 'glitter': False, 'bump': False, 'scream': False},
[-1], False, False, {}
)
agent.TELL(step_result_1)
# Agent dies at [2,1]
step_result_2 = (
{'x': 1, 'y': 0, 'stench': False, 'breeze': False, 'glitter': False, 'bump': False, 'scream': False},
[-1000], True, False, {}
)
agent.TELL(step_result_2)
print("\n🔍 Testing queries:")
agent.ASK("W11") # Should be FALSE (survived)
agent.ASK("P11") # Should be FALSE (survived)
agent.ASK("W21") # Should be TRUE (died)
agent.ASK("P21") # Should be False No breez at 11
agent.ASK("W12") # Should be TRUE (from S11)
agent.ASK("W22") # Should be TRUE (from S11)
agent.print_kb()
if __name__ == "__main__":
print("🏰 Horn Clauses Agent for Wumpus World")
print("="*60)
test_horn_gym_integration()
print("\n✨ Horn implementation complete!")
print(" Shows clear limitations vs CNF Resolution")