簡単なSMT&SATソルバーを書いて4x4数独を解く

作成日: 2024-10-22
 
SMTソルバーとSATソルバーを実装したので、その実装のメモです。リポジトリはこちら
主に次の記事を参考にしました。

SMTソルバー

SMTソルバーは論理式を満たす変数を求めるソルバーです。
X, Yが0か1の変数として、命題X≠YをSMTソルバーの表現で表すと次のようになります。この命題を満たすのは(X, Y)=(0, 1),(1, 0)です。どちらかを発見して、SMTソルバーは終了します。
(int x 0 1)
(int y 0 1)
(not (eq x y))

数値表現

数値表現を真か偽に割り当てるように論理式を変形します。x0が偽ならX=0、真ならX=1です。X=Yの部分が次のように翻訳されます。数値表現には様々ありますが、今回は一番単純な表現を実装しています。
(not (or (and x0 y0) (and (not x0) (not y0))))

CNF表現

さらに、命題をCNF(和積標準形)に変形します。ORの部分は変数を補うことによって、命題の数を抑えてCNFに変形することが出来ます。
!x0 or !$or_1 
!y0 or !$or_2 
$or_1 or $or_2 
x0 or !$or_3 
y0 or !$or_4 
$or_3 or $or_4

SAT表現

最後に変数を数字に置換することで、SATソルバーが解ける形になります。SMTソルバーは命題をSAT表現に変換して、割り当てられた結果を元の変数に戻します。
c vars ["x0", "$or_1", "y0", "$or_2", "$or_3", "$or_4"]
-1 -2 0
-3 -4 0
2 4 0
1 -5 0
3 -6 0
5 6 0

SATソルバー

最後に、SAT表現を解けばタスク完了です。
単純なDPLLアルゴリズムと、より賢いCDCLアルゴリズムを実装しました。
DPLLは単純なバックトラックです。50命題変数ぐらいなら解けますが、実用性はありません。
CDCLはDPLLに矛盾学習とバックジャンプを実装したものです。CDCLは4x4の数独の約1000変数が解けました。
9x9の数独は、およそ50000変数のSATになっており、単純なCDCLでは解けませんでした。SAT表現への変換の工夫と、CDCLの変数決定ヒューリスティックが必要になります。
世の中にあるMiniSatやZ3はかなり大きめの問題を解けるので素晴らしいですね。

関連

SMTソルバーを使って自作ペンシルパズルを解く話です。