import itertools
|
|
|
|
|
|
class Sentence():
|
|
|
|
def evaluate(self, model):
|
|
"""Evaluates the logical sentence."""
|
|
raise Exception("nothing to evaluate")
|
|
|
|
def formula(self):
|
|
"""Returns string formula representing logical sentence."""
|
|
return ""
|
|
|
|
def symbols(self):
|
|
"""Returns a set of all symbols in the logical sentence."""
|
|
return set()
|
|
|
|
@classmethod
|
|
def validate(cls, sentence):
|
|
if not isinstance(sentence, Sentence):
|
|
raise TypeError("must be a logical sentence")
|
|
|
|
@classmethod
|
|
def parenthesize(cls, s):
|
|
"""Parenthesizes an expression if not already parenthesized."""
|
|
def balanced(s):
|
|
"""Checks if a string has balanced parentheses."""
|
|
count = 0
|
|
for c in s:
|
|
if c == "(":
|
|
count += 1
|
|
elif c == ")":
|
|
if count <= 0:
|
|
return False
|
|
count -= 1
|
|
return count == 0
|
|
if not len(s) or s.isalpha() or (
|
|
s[0] == "(" and s[-1] == ")" and balanced(s[1:-1])
|
|
):
|
|
return s
|
|
else:
|
|
return f"({s})"
|
|
|
|
|
|
class Symbol(Sentence):
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, Symbol) and self.name == other.name
|
|
|
|
def __hash__(self):
|
|
return hash(("symbol", self.name))
|
|
|
|
def __repr__(self):
|
|
return self.name
|
|
|
|
def evaluate(self, model):
|
|
try:
|
|
return bool(model[self.name])
|
|
except KeyError:
|
|
raise Exception(f"variable {self.name} not in model")
|
|
|
|
def formula(self):
|
|
return self.name
|
|
|
|
def symbols(self):
|
|
return {self.name}
|
|
|
|
|
|
class Not(Sentence):
|
|
def __init__(self, operand):
|
|
Sentence.validate(operand)
|
|
self.operand = operand
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, Not) and self.operand == other.operand
|
|
|
|
def __hash__(self):
|
|
return hash(("not", hash(self.operand)))
|
|
|
|
def __repr__(self):
|
|
return f"Not({self.operand})"
|
|
|
|
def evaluate(self, model):
|
|
return not self.operand.evaluate(model)
|
|
|
|
def formula(self):
|
|
return "¬" + Sentence.parenthesize(self.operand.formula())
|
|
|
|
def symbols(self):
|
|
return self.operand.symbols()
|
|
|
|
|
|
class And(Sentence):
|
|
def __init__(self, *conjuncts):
|
|
for conjunct in conjuncts:
|
|
Sentence.validate(conjunct)
|
|
self.conjuncts = list(conjuncts)
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, And) and self.conjuncts == other.conjuncts
|
|
|
|
def __hash__(self):
|
|
return hash(
|
|
("and", tuple(hash(conjunct) for conjunct in self.conjuncts))
|
|
)
|
|
|
|
def __repr__(self):
|
|
conjunctions = ", ".join(
|
|
[str(conjunct) for conjunct in self.conjuncts]
|
|
)
|
|
return f"And({conjunctions})"
|
|
|
|
def add(self, conjunct):
|
|
Sentence.validate(conjunct)
|
|
self.conjuncts.append(conjunct)
|
|
|
|
def evaluate(self, model):
|
|
return all(conjunct.evaluate(model) for conjunct in self.conjuncts)
|
|
|
|
def formula(self):
|
|
if len(self.conjuncts) == 1:
|
|
return self.conjuncts[0].formula()
|
|
return " ∧ ".join([Sentence.parenthesize(conjunct.formula())
|
|
for conjunct in self.conjuncts])
|
|
|
|
def symbols(self):
|
|
return set.union(*[conjunct.symbols() for conjunct in self.conjuncts])
|
|
|
|
|
|
class Or(Sentence):
|
|
def __init__(self, *disjuncts):
|
|
for disjunct in disjuncts:
|
|
Sentence.validate(disjunct)
|
|
self.disjuncts = list(disjuncts)
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, Or) and self.disjuncts == other.disjuncts
|
|
|
|
def __hash__(self):
|
|
return hash(
|
|
("or", tuple(hash(disjunct) for disjunct in self.disjuncts))
|
|
)
|
|
|
|
def __repr__(self):
|
|
disjuncts = ", ".join([str(disjunct) for disjunct in self.disjuncts])
|
|
return f"Or({disjuncts})"
|
|
|
|
def evaluate(self, model):
|
|
return any(disjunct.evaluate(model) for disjunct in self.disjuncts)
|
|
|
|
def formula(self):
|
|
if len(self.disjuncts) == 1:
|
|
return self.disjuncts[0].formula()
|
|
return " ∨ ".join([Sentence.parenthesize(disjunct.formula())
|
|
for disjunct in self.disjuncts])
|
|
|
|
def symbols(self):
|
|
return set.union(*[disjunct.symbols() for disjunct in self.disjuncts])
|
|
|
|
|
|
class Implication(Sentence):
|
|
def __init__(self, antecedent, consequent):
|
|
Sentence.validate(antecedent)
|
|
Sentence.validate(consequent)
|
|
self.antecedent = antecedent
|
|
self.consequent = consequent
|
|
|
|
def __eq__(self, other):
|
|
return (isinstance(other, Implication)
|
|
and self.antecedent == other.antecedent
|
|
and self.consequent == other.consequent)
|
|
|
|
def __hash__(self):
|
|
return hash(("implies", hash(self.antecedent), hash(self.consequent)))
|
|
|
|
def __repr__(self):
|
|
return f"Implication({self.antecedent}, {self.consequent})"
|
|
|
|
def evaluate(self, model):
|
|
return ((not self.antecedent.evaluate(model))
|
|
or self.consequent.evaluate(model))
|
|
|
|
def formula(self):
|
|
antecedent = Sentence.parenthesize(self.antecedent.formula())
|
|
consequent = Sentence.parenthesize(self.consequent.formula())
|
|
return f"{antecedent} => {consequent}"
|
|
|
|
def symbols(self):
|
|
return set.union(self.antecedent.symbols(), self.consequent.symbols())
|
|
|
|
|
|
class Biconditional(Sentence):
|
|
def __init__(self, left, right):
|
|
Sentence.validate(left)
|
|
Sentence.validate(right)
|
|
self.left = left
|
|
self.right = right
|
|
|
|
def __eq__(self, other):
|
|
return (isinstance(other, Biconditional)
|
|
and self.left == other.left
|
|
and self.right == other.right)
|
|
|
|
def __hash__(self):
|
|
return hash(("biconditional", hash(self.left), hash(self.right)))
|
|
|
|
def __repr__(self):
|
|
return f"Biconditional({self.left}, {self.right})"
|
|
|
|
def evaluate(self, model):
|
|
return ((self.left.evaluate(model)
|
|
and self.right.evaluate(model))
|
|
or (not self.left.evaluate(model)
|
|
and not self.right.evaluate(model)))
|
|
|
|
def formula(self):
|
|
left = Sentence.parenthesize(str(self.left))
|
|
right = Sentence.parenthesize(str(self.right))
|
|
return f"{left} <=> {right}"
|
|
|
|
def symbols(self):
|
|
return set.union(self.left.symbols(), self.right.symbols())
|
|
|
|
|
|
def model_check(knowledge, query):
|
|
"""Checks if knowledge base entails query."""
|
|
|
|
def check_all(knowledge, query, symbols, model):
|
|
"""Checks if knowledge base entails query, given a particular model."""
|
|
|
|
# If model has an assignment for each symbol
|
|
if not symbols:
|
|
|
|
# If knowledge base is true in model, then query must also be true
|
|
if knowledge.evaluate(model):
|
|
return query.evaluate(model)
|
|
return True
|
|
else:
|
|
|
|
# Choose one of the remaining unused symbols
|
|
remaining = symbols.copy()
|
|
p = remaining.pop()
|
|
|
|
# Create a model where the symbol is true
|
|
model_true = model.copy()
|
|
model_true[p] = True
|
|
|
|
# Create a model where the symbol is false
|
|
model_false = model.copy()
|
|
model_false[p] = False
|
|
|
|
# Ensure entailment holds in both models
|
|
return (check_all(knowledge, query, remaining, model_true) and
|
|
check_all(knowledge, query, remaining, model_false))
|
|
|
|
# Get all symbols in both knowledge and query
|
|
symbols = set.union(knowledge.symbols(), query.symbols())
|
|
|
|
# Check that knowledge entails query
|
|
return check_all(knowledge, query, symbols, dict())
|