For AI agents: the complete documentation index is available at https://shijin.space/llms.txt, the full documentation bundle is available at https://shijin.space/llms-full.txt, and this page is available as Markdown at https://shijin.space/knowledge/sat-dpll/index.md.

SAT 与 DPLL

前置知识:布尔运算、集合和递归。阅读路线见专题概览。

文字、子句和合取范式

布尔变量取值于 {0,1}\{0,1\},分别表示假和真。变量 xx 及其否定 ¬x\neg x 称为文字(literal);若 ℓ\ell 是一个文字,¬ℓ\neg\ell 表示其互补文字,因而 ¬(¬x)=x\neg(\neg x)=x。

子句(clause)是若干文字的析取,合取范式(conjunctive normal form,CNF)是若干子句的合取。例如

F=(a∨b)∧(¬a∨c)∧(¬b∨c).F=(a\lor b)\land(\neg a\lor c)\land(\neg b\lor c).

赋值 a=1,b=0,c=1a=1,b=0,c=1 使每个子句都为真,因此是 FF 的一个满足赋值,也称模型。SAT 判断模型是否存在;它不要求找出所有模型 [1, sec. 3.2]。

为便于描述算法,也把子句写成文字集合,把 CNF 写成子句集合:

F={{a,b},{¬a,c},{¬b,c}}.F=\{\{a,b\},\{\neg a,c\},\{\neg b,c\}\}.

这个记法有两个容易混淆的边界情况:

  • 空公式 F=∅F=\varnothing 没有需要满足的子句,值为真。
  • 空子句 ∅∈F\varnothing\in F 没有能使该子句为真的文字,因此整个公式不可满足。

二者分别是搜索成功和冲突的基本判据 [1, pp. 101–102]。含有 xx 和 ¬x\neg x 的子句恒真,可以直接删除;重复文字可以合并。

从命题公式到 CNF:Tseitin 编码

实际问题得到的命题公式未必已经是 CNF。直接使用分配律可以得到逻辑等价的 CNF,但反复展开可能使公式规模指数增长。Tseitin 编码给子公式引入辅助变量,用局部约束描述其含义,再要求代表整个公式的变量为真。对于由固定元数的常见布尔运算构成的公式,辅助变量数和编码规模均可保持为输入规模的线性量级 [2, secs. 2.2.1–2.2.2]。

考虑

φ=(a∧b)∨¬c.\varphi=(a\land b)\lor\neg c.

引入 zz 表示 a∧ba\land b,引入 tt 表示整个公式:

z↔(a∧b),t↔(z∨¬c).z\leftrightarrow(a\land b),\qquad t\leftrightarrow(z\lor\neg c).

每个双向定义都可以拆成 CNF。对第一个定义,z=1z=1 要求 a=b=1a=b=1,反过来 a=b=1a=b=1 要求 z=1z=1;第二个定义也同时保留两个方向。因此得到

Dz=(¬z∨a)∧(¬z∨b)∧(z∨¬a∨¬b),Dt=(¬t∨z∨¬c)∧(t∨¬z)∧(t∨c),T=Dz∧Dt∧(t).\begin{aligned} D_z&=(\neg z\lor a)\land(\neg z\lor b) \land(z\lor\neg a\lor\neg b),\\ D_t&=(\neg t\lor z\lor\neg c) \land(t\lor\neg z)\land(t\lor c),\\ T&=D_z\land D_t\land(t). \end{aligned}

最后的单位子句 (t)(t) 不可省略。Dz∧DtD_z\land D_t 只规定辅助变量应如何取值,没有要求原公式成立。例如 a=b=0,c=1,z=t=0a=b=0,c=1,z=t=0 满足两个定义,却使 φ\varphi 为假;加入 (t)(t) 才会排除这个赋值。

这里需要区分三个层次:

  • 逻辑等价要求对相关变量的每个赋值,两边的真假相同。
  • 等可满足只要求两边同时有解或同时无解,不自动给出模型之间的对应关系。
  • 模型投影与扩展说明怎样在两个公式的模型之间转换。本例中,TT 的任意模型限制到 a,b,ca,b,c 后都满足 φ\varphi;反过来,φ\varphi 的任意模型都能按子公式的值补出 z,tz,t,得到 TT 的模型。

因而,本例不仅等可满足,还满足逐个原变量赋值的对应关系:

φ(a,b,c)≡∃z ∃t  T(a,b,c,z,t).\varphi(a,b,c)\equiv\exists z\,\exists t\;T(a,b,c,z,t).

不能直接把它写成扩展变量集合上的 φ≡T\varphi\equiv T。例如 a=b=c=1a=b=c=1 满足 φ\varphi,但任意补一个 z=0z=0 就会违反 DzD_z;正确的扩展应取 z=t=1z=t=1。引入辅助变量后,SAT 求解器处理的是 TT,应用端需要的是投影回原变量的模型 [2, sec. 2.2.2]。

线性规模编码避免了转换阶段的无谓膨胀,不保证编码后的实例容易求解;编码产生的传播能力和搜索结构仍会影响实际性能。

部分赋值与公式化简

部分赋值只给一部分变量指定值。未赋值是求解器中的状态,不是布尔逻辑的第三种真值。一个尚未满足的子句可能仍有未赋值文字,因此尚未满足不等于已经冲突。

令文字 ℓ\ell 为真,公式限制 F∣ℓF|_\ell 通过两步得到:删除包含 ℓ\ell 的子句,并从其余子句中删除 ¬ℓ\neg\ell。前一步删除已经满足的约束,后一步删除已经为假的选择 [1, sec. 3.2.2]。

例如

F={{a,b},{¬a,c},{¬b,c}},F∣a={{c},{¬b,c}}.\begin{aligned} F&=\{\{a,b\},\{\neg a,c\},\{\neg b,c\}\},\\ F|_a&=\{\{c\},\{\neg b,c\}\}. \end{aligned}

这里的剩余公式描述的是固定 a=1a=1 之后的子问题。它与任意赋值下的原公式并不等价;应把已经固定的赋值与剩余公式一起理解。

单位传播:先完成被迫的赋值

如果一个子句中所有文字都为假,只剩一个未赋值文字 ℓ\ell,那么任何扩展当前部分赋值的模型都必须令 ℓ\ell 为真。这就是单位传播(unit propagation,也常称 BCP)。传播得到的新赋值又可能使其他子句变成单位子句,因此需要反复执行,直到没有可传播的文字或出现冲突 [1, sec. 3.5.2]。

考虑

F=(a)∧(¬a∨b)∧(¬b∨c)∧(¬c).F=(a)\land(\neg a\lor b)\land(\neg b\lor c)\land(\neg c).

按照从左到右的一种传播顺序,单位子句 (a)(a) 强制 a=1a=1,随后依次强制 b=1b=1 和 c=1c=1;此时 (¬c)(\neg c) 为假,检测到冲突。选择另一种传播顺序也会检测到冲突,但最先返回的冲突子句可能不同。

传播结束而没有冲突,不能据此回答 SAT。 例如

G=(x∨y)∧(x∨¬y)∧(¬x∨y)∧(¬x∨¬y)G=(x\lor y)\land(x\lor\neg y) \land(\neg x\lor y)\land(\neg x\lor\neg y)

没有单位子句,但不可满足:x=1x=1 后,后两个子句分别要求 y=1y=1 和 y=0y=0;x=0x=0 后,前两个子句给出同样的矛盾。单位传播是高效但不完备的推理过程,需要与搜索或更强推理结合 [1, pp. 103, 115–116]。

DPLL:传播之后划分搜索空间

DPLL 在当前赋值下先做单位传播;若既没有冲突,也没有满足全部子句,就选择一个未赋值变量 xx,分别搜索 x=1x=1 和 x=0x=0。下面用伪代码表示这一过程,初始调用为 DPLL⁡(F,∅)\operatorname{DPLL}(F,\varnothing):返回部分满足赋值表示 SAT,返回 UNSAT\mathrm{UNSAT} 表示无解。辅助过程的具体实现未展开,伪代码不能独立运行。

Algorithm 1 DPLL

1:function DPLL(F,αF, \alpha)

2:(F,α)←(F, \alpha) \gets PropagateToFixpoint(F,αF, \alpha)

3:if ∅∈F\varnothing \in F then

4:return Unsat

5:if F=∅F = \varnothing then

6:return α\alpha

7:x←x \gets ChooseVariable(FF)

8:for all ℓ∈{x,¬x}\ell \in \{x, \neg x\} do

9:F′←F' \gets Condition(F,ℓF, \ell)

10:α′←\alpha' \gets Extend(α,ℓ\alpha, \ell)

11:m←m \gets DPLL(F′,α′F', \alpha')

12:if m≠UNSATm \ne \mathrm{UNSAT} then

13:return mm

14:return Unsat

这里将 FF 视为子句集合:∅∈F\varnothing\in F 表示出现空子句,F=∅F=\varnothing 表示所有子句均已满足。PropagateToFixpoint 做单位传播直到不再产生新赋值或发现冲突;ChooseVariable 选择剩余公式中的未赋值变量。Condition 将文字 ℓ\ell 置真并返回该分支的剩余公式,Extend 返回扩展后的赋值副本,后续传播也只更新当前分支。空赋值也可能满足空公式,因此用 m≠UNSATm\ne\mathrm{UNSAT} 判断是否找到模型。

每个分支拥有自己的剩余公式和赋值,失败后撤销该分支的决定与传播。若全部子句已经满足,尚未赋值的变量可以任意补全。纯文字消除可以作为额外化简,但不是这个核心过程所必需的 [1, sec. 3.5]。

其正确性来自三个事实:单位传播只做当前子问题中必需的赋值;x=1x=1 与 x=0x=0 覆盖所有可能模型;每次分支都固定一个新变量,所以有限变量上的搜索树有限。不设资源限制、完整处理两个分支时,该过程会终止并给出正确结论。完备性并不提供多项式时间保证。

归结:把两个约束合成一个逻辑后果

若两个子句分别包含 xx 和 ¬x\neg x,可对 xx 做归结(resolution):

A∨xB∨¬xA∨B.\frac{A\lor x\qquad B\lor\neg x}{A\lor B}.

若 AA 与 BB 都为假,两个前提就分别要求 x=1x=1 和 x=0x=0,不可能同时满足,因此归结式是前提的逻辑后果。这里 A,BA,B 表示不包含被归结变量的剩余文字析取。

加入归结式保留原公式的所有模型;它不同于直接删除两个前提。DP 变量消除需要考虑涉及某变量的正负子句对,加入相应归结式后再删除包含该变量的子句,其结果对应对该变量做存在量化 [1, secs. 3.2.1, 3.3.1]。

对不可满足的 CNF,完整的命题归结可以推导出空子句,称为反驳完备性;单位归结的限制使它没有这个一般保证 [1, pp. 102–103]。

DP 与 DPLL:消去变量,还是保留分支

归结可以用于两种不同的工作:加入一个已经隐含的约束,或者在保留可满足性的前提下消去变量。DP(Davis–Putnam)采用后一条路线;DPLL(Davis–Putnam–Logemann–Loveland)则通过分支搜索处理变量。两者都能用于 SAT 判定,但维护的中间对象不同 [1, secs. 3.3.1, 3.5]。

用一个小公式比较这两种操作:

F=(x∨a)∧(¬x∨b).F=(x\lor a)\land(\neg x\lor b).

DP 消去 xx。 对两个子句归结,得到 (a∨b)(a\lor b);加入归结式后,再删除所有含 xx 或 ¬x\neg x 的子句,剩下

G=(a∨b),G≡∃x  F.G=(a\lor b),\qquad G\equiv\exists x\;F.

这个等价关系的含义是:对 a,ba,b 的任意赋值,GG 成立,当且仅当能够选一个 xx 使 FF 成立。若 a=0,b=1a=0,b=1,可以取 x=1x=1;若 a=1,b=0a=1,b=0,应取 x=0x=0;若 a=b=1a=b=1,两种取值都可以;若 a=b=0a=b=0,不存在可行的 xx [1, pp. 104–105]。

DPLL 对 xx 分支。 固定 xx 后分别得到

F∣x=(b),F∣¬x=(a).F|_x=(b),\qquad F|_{\neg x}=(a).

第一条分支用单位传播令 b=1b=1,第二条分支用单位传播令 a=1a=1。它们合起来覆盖所有原公式模型;进行 SAT 判定时,一条分支找到模型即可停止,只有要回答 UNSAT 时才必须排除所有尚未排除的分支。在剩余变量上,两条分支的析取满足

∃x  F≡F∣x∨F∣¬x≡b∨a.\exists x\;F\equiv F|_x\lor F|_{\neg x}\equiv b\lor a.

DP 将这个存在性条件直接保存在一个消去 xx 的公式中;DPLL 保留分支取值,在各分支中继续化简和搜索。DP 的代价在于生成归结式:若 xx 正向出现于 mm 个子句、负向出现于 nn 个子句,消元时至多要考察 mnmn 对前提,连续消元可能导致中间公式膨胀。DPLL 避免预先生成全部这些归结式,但搜索树同样可能指数增长,不能仅凭采用分支就获得多项式时间保证 [1, secs. 3.3.1, 3.5]。

加入归结式与消元后的模型含义不同。 F∧(a∨b)F\land(a\lor b) 与 FF 逻辑等价;消元结果 GG 却只约束 a,ba,b。例如 a=1,b=0a=1,b=0 满足 GG,若随意补成 x=1x=1,第二个原始子句就为假;恢复原问题的模型必须选一个可行的 xx。

CDCL 在搜索过程中按冲突需要生成学习子句。在本文不含额外公式变换的基本流程中,它将逻辑后果加入当前公式,并不因此删除产生这些结论的原始约束。这是搜索与归结协作的方式,后面的冲突分析将给出具体推导。

从回溯走向学习

DPLL 可以证明一个分支失败,却未必把失败原因保存为一个可在别处使用的约束。若多个分支由于同一组相关赋值而冲突,简单搜索可能重复发现同一个问题。

CDCL保留搜索框架,同时记录传播原因,在冲突时通过归结导出学习子句。新子句排除造成冲突的赋值组合,并指导回跳后应传播什么;这把当前分支的失败转换成后续搜索可以复用的推理结果 [1, secs. 3.6.2–3.6.4]。

参考文献

1. Adnan Darwiche and Knot Pipatsrisawat. Complete algorithms. Handbook of satisfiability. 2021. DOI.

2. Steven Prestwich. CNF encodings. Handbook of satisfiability. 2021. DOI.