-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolution_refutation.py
More file actions
149 lines (101 loc) · 4.13 KB
/
resolution_refutation.py
File metadata and controls
149 lines (101 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import argparse
Literal = tuple[str, bool]
Clause = frozenset[Literal]
def parse_literal(token: str) -> Literal:
token = token.strip()
if not token:
raise ValueError("Empty literal found in clause")
if token.startswith("!"):
return token[1:].strip(), True
return token, False
def negate_literal(literal: Literal) -> Literal:
symbol, is_negated = literal
return symbol, not is_negated
def parse_clause(expression: str) -> Clause:
expression = expression.strip().replace(" ", "")
if not expression:
raise ValueError("Empty clause found in input")
literals = [parse_literal(token) for token in expression.split("|")]
return frozenset(literals)
def read_problem(path: str) -> tuple[list[Clause], Clause]:
with open(path, "r", encoding="utf-8") as file:
lines = [line.strip() for line in file if line.strip()]
if len(lines) < 2:
raise ValueError("Input must contain a clause count, knowledge base, and query")
clause_count = int(lines[0])
expected_line_count = clause_count + 2
if len(lines) != expected_line_count:
raise ValueError(
f"Expected {expected_line_count} non-empty lines, found {len(lines)}"
)
knowledge_base = [parse_clause(line) for line in lines[1 : clause_count + 1]]
query = parse_clause(lines[clause_count + 1])
return knowledge_base, query
def negate_query(query: Clause) -> list[Clause]:
return [frozenset([negate_literal(literal)]) for literal in sorted(query)]
def is_tautology(clause: Clause) -> bool:
return any(negate_literal(literal) in clause for literal in clause)
def resolve_pair(left: Clause, right: Clause) -> list[Clause]:
resolvents = []
for literal in left:
complement = negate_literal(literal)
if complement not in right:
continue
resolvent = (left - {literal}) | (right - {complement})
if not is_tautology(resolvent):
resolvents.append(frozenset(resolvent))
return resolvents
def format_literal(literal: Literal) -> str:
symbol, is_negated = literal
return f"!{symbol}" if is_negated else symbol
def format_clause(clause: Clause) -> str:
if not clause:
return "{}"
return " | ".join(format_literal(literal) for literal in sorted(clause))
def resolution_refutation(clauses: list[Clause], show_steps: bool = True) -> bool:
known_clauses = list(dict.fromkeys(clauses))
step = 1
while True:
new_clauses = []
for left_index, left_clause in enumerate(known_clauses):
for right_clause in known_clauses[left_index + 1 :]:
for resolvent in resolve_pair(left_clause, right_clause):
if resolvent in known_clauses or resolvent in new_clauses:
continue
if show_steps:
print(
f"{step}: RESOLVE ({format_clause(left_clause)}) "
f"and ({format_clause(right_clause)}) "
f"=> {format_clause(resolvent)}"
)
step += 1
if not resolvent:
return True
new_clauses.append(resolvent)
if not new_clauses:
return False
known_clauses.extend(new_clauses)
def prove(path: str, show_steps: bool = True) -> bool:
knowledge_base, query = read_problem(path)
clauses = knowledge_base + negate_query(query)
return resolution_refutation(clauses, show_steps=show_steps)
def main() -> None:
parser = argparse.ArgumentParser(
description="Prove propositional entailment using resolution refutation."
)
parser.add_argument(
"input_file",
nargs="?",
default="input.txt",
help="Path to the input file. Defaults to input.txt.",
)
parser.add_argument(
"--quiet",
action="store_true",
help="Only print the final entailment result.",
)
args = parser.parse_args()
result = prove(args.input_file, show_steps=not args.quiet)
print(1 if result else 0)
if __name__ == "__main__":
main()