归结原理
归结原理(resolution principle)是一种从子句推出新子句的推理规则。命题逻辑中的一次归结,从 和 推出 ;互补文字 被消去。将待证结论的否定与前提一同写成合取范式,若反复归结推出空子句,就证明这些公式不能同时为真。[1]
本文先讨论只有有限个命题变量的情形。一阶逻辑归结还涉及量词处理、变量改名和合一,不能直接把命题版本的有限枚举结论搬过去。
一次归结保留的逻辑信息
用集合表示子句时, 和 表示剩余文字的析取。取 、,归结式为 。若 假,第一条前提强迫 真;若 真,第二条强迫 真。无论哪种情况, 都真。这证明规则的可靠性:任何满足两个亲本子句的赋值,也满足归结式。
归结式通常比两个亲本的合取弱,不能据此删除亲本而保持等价。例如 满足 ,却不满足 。可靠性保证的是前提蕴含归结式,而不是两边逻辑等价。
每次二元归结只选一对互补文字。由 与 对 归结,得到的是重言子句 。若把两对互补文字同时删掉而得到空子句,就会把一个可满足的前提集误判为矛盾;例如 已同时满足两个前提。
从四条约束得到空子句
考虑教学用子句集 它要求 至少一真,而两者任一为真又会经约束迫使 为真,最后一条却要求 假。归结把这个矛盾写成可逐步核验的推导:
空子句是零个文字的析取,恒为假。每一步归结保持“原前提的任意模型也满足已推导子句”这个性质,故一旦推出空子句,原前提不可能有模型。这是归结反证法的正确性依据。
若想证明原来三个子句 蕴含 ,便可临时加入结论的否定 ,使用上述反驳。一般地, 当且仅当 不可满足。若删去 ,推导停在 ;此时 满足剩余前提,故不能再声称蕴含 。
命题归结的反驳完备性
反驳完备性说:有限命题子句集不可满足时,存在有限归结推导得到空子句。它不表示归结单独就能导出每一个语法形式的有效公式。
以下通过消去一个变量证明。选变量 ,删除重言子句和重复文字后,把子句分成三类:不含 的 ,含正文字的 ,含负文字的 。构造新集合 ,保留 ,并对每对正负亲本加入 ,然后删去全部含 的亲本。
原集合的模型限制到其余变量后一定满足 ,这是归结可靠性。反向考虑任意满足 的赋值:若某个 假,就必须取 ;这时所有 都真,否则对应归结式 为假。若没有 假,所有正亲本已真;可取 使全部负亲本为真。因此新赋值总能扩展成原集合的模型。新旧集合等可满足。
对变量数作归纳。零个变量时,不可满足的子句集必含空子句。多于零个变量时,若原集合不可满足,消元后的 也不可满足;按归纳假设,它有空子句的归结推导。 中每条新增子句本来就可由原集合一步归结得到,把这些推导接在前面,就得到原集合的反驳。变量每次减少一个,归纳有限结束,完备性得证。
一个有限饱和程序
下面用非零整数表示文字,例如 1 为 、-1 为 。函数复制输入,不改写调用者的子句;返回 True 表示已推导矛盾,返回 False 表示归结闭包中没有空子句,按上述完备性即为可满足。它是教学实现,未加入现代 SAT 求解器的学习、回跳或启发式。
def resolution_unsat(source):
clauses, seen = [], set()
for row in source:
clause = frozenset(row)
if any(type(x) is not int or x == 0 for x in clause):
raise ValueError("literals must be nonzero integers")
if not clause:
return True
if any(-x in clause for x in clause):
continue
if clause not in seen:
seen.add(clause)
clauses.append(clause)
i = 0
while i < len(clauses):
for j in range(i):
for pivot in clauses[i]:
if -pivot not in clauses[j]:
continue
child = (clauses[i] - {pivot}) | (clauses[j] - {-pivot})
if not child:
return True
if any(-x in child for x in child):
continue
if child not in seen:
seen.add(child)
clauses.append(child)
i += 1
return False
example = [{1, 2}, {-1, 3}, {-2}, {-3}]
assert resolution_unsat(example)
assert not resolution_unsat([{1, 2}, {-1, -2}])
assert not resolution_unsat([])
assert resolution_unsat([[]])
assert not resolution_unsat([{1, -1}])
print("all checks passed")
已出现 个变量时,非重言子句中的每个变量有缺席、正、负三种状态,所以至多有 种。程序只在首次出现时加入子句,因此终止;每对不同子句都会在较晚者被处理时检查全部互补文字,终止时达到归结闭包。用普通集合运算估计,每对子句至多尝试 个主元,每次合并和重言检查需 时间,给出宽松上界 ,子句存储为 ;这不计输入读取与去重的成本。指数上界说明“存在完整有限算法”并不等于“大型问题容易求解”。
参考文献
- ↑ Ruben Martins,Lecture Notes on Resolution,Carnegie Mellon University,2024,第3—6节。