数独を一瞬で解く by SATソルバー

SATソルバーを用いて，パズル・数独を解いたのでここにまとめます．

MacとLinux両方で本記事の数独ソルバーが動くことを確認しています．

記事の内容が，SATソルバーを触ったことのない人にとって難しくならないように努めます．

# SATとは
SAT(boolean SATisfiability testing)は，命題論理式の充足可能性判定[^1]のことで，CNF (Conjuctive Normal Form) を扱うことが一般的とされています．

### CNF式とは
CNFは論理学とかで出てくる連言標準形のことです．

用語の説明をします．

- リテラル : 変数x，またはその否定$\lnot$xのこと
- 節 : リテラルを論理和$\lor$で結合した論理式のこと
- CNF式 : 節を論理積$\land$で結合した論理式のこと

CNFの例を以下に示します．もちろん，変数$x$は 0 か 1 をとります．

$$(x_1 \lor \lnot x_2) \land x_3 \land (\lnot x_1 \lor \lnot x_2) $$

以上を満たす解 (全体が1となるような値割り当て) は，$x_1 = 1,x_2 = 0,x_3=1$があります．

**SATソルバー**とはこれを解くソルバーのことです．


### ソルバーのインストール
Mac，Linuxなら以下のシェルスクリプトを実行するとSATソルバー clasp3.3.4 が手に入ります．

```bash:install_clasp334.sh
cd /usr/local/src
wget https://github.com/potassco/clasp/releases/download/v3.3.4/clasp-3.3.4-source.tar.gz
tar xvzf clasp-3.3.4-source.tar.gz
cd clasp-3.3.4
cmake -H. -B/usr/local/src/clasp-3.3.4
cmake --build /usr/local/src/clasp-3.3.4
cmake --build /usr/local/src/clasp-3.3.4 --target install
cp -p bin/clasp /usr/local/bin/clasp334
ln -s clasp334 clasp
```

### DIMAC CNF形式
ソルバーのCNF式の入力は，DIMACS CNF形式と呼ばれる整数列で簡潔に記述されることが多いです．

$x_1$を1，$x_2$を2，$x_3$を3という整数に割り当てたとき，上記のCNF式は以下のようなDIMACS CNF形式でかかれます．

```:sample.cnf
p cnf 3 3
1 -2 0
3 0
-1 -2 0
```

$x_1$の否定は-1と表現します．

DIMACS CNFは「p cnf "変数の数" "節の数"」から始まります．

各行が節を表していて，節の終わりには0を書いてあげます．

簡単ですね．

端末やターミナルから，以下のコマンドで clasp を起動して解くことができると思います．
```$ clasp sample.cnf```

# 数独を解く準備
上記のシェルスクリプトのように，ソルバーはインストールしておいてください．

では，数独を解く準備をしていきます．

具体的には，問題の情報を詰め込んだCNFを作ります．[^2]

### 問題の条件

Wikipedia[^3] によると，以下のルールに基づいたパズルだということです．

- 空いているマスに1〜9のいずれかの数字を入れる
- 縦・横の各列及び、太線で囲まれた3×3のブロック内に同じ数字が複数入ってはいけない

![sudoku.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296126/baa6ac9c-0b8f-52c7-4817-a47d4342c836.png)

自分は，こういう風にリテラルを作りました．

左上に入るマスにある数字$x(1 \leq x \leq 9)$が入るとき，そのリテラルを$x$とする．

その右のマスにある数字$y(1 \leq y \leq 9)$が入るとき，$y+9$となるような数字をリテラルにする...って急に言われても困りますよね．．．

例えば，写真の数独をDIMACS CNFで表現する時，

```
5 0 # 左上の5
12 0 # その右の3
43 0 # 一番上の行の7
87 0 # 左上から一つ下の6
...
```

みたいな感じでリテラルを与えました．

### ルールの条件
数独は"同じ行に同じ数字が入ってはいけない．"というルールでした．

それでは"一番上の行に，1は1回しか現れない"という制約を書いてみましょう．

```
# 一番上の行で，1は一回以上現れる．(at-least-one制約)
1 10 19 28 37 46 55 64 73 0

# 一番上の行で，1はたかだか一回現れる．(at-most-one制約)
-1 -10 0
-1 -19 0
-1 -28 0
-1 -37 0
-1 -46 0
-1 -55 0
-1 -64 0
-1 -73 0
-10 -19 0
-10 -28 0
-10 -37 0
-10 -46 0
-10 -55 0
-10 -64 0
-10 -73 0
-19 -28 0
-19 -37 0
...
(全ての組み合わせを重複なく書いてください！) 
...
-46 -55 0
-46 -64 0
-46 -73 0
-55 -64 0
-55 -73 0
-64 -73 0
```

at-least-one制約はなくても解けるのですが，あった方が早く解けるので入れてみてください．

ここは「1 または　10 または　19　または ... または 73」ってイメージです．

at-most-one制約は，上記のようにpairwise法で書き下したとき，多分，${}_9C_2$節あります．

ここは「(1の否定 または　10の否定)　かつ　(1の否定　または　19の否定)　...」って感じです．

これを1だけでなく，9までの数字全てで作ります．

さらに，行だけでなく列，ブロックに関しても作っていきます．

すると，以下のようなcnfファイルが完成します．

https://github.com/okayu1230z/NumberPlaceSolver/blob/master/toi_001.cnf

変数の数は729，節数は12018のCNFになりました．

# 数独を解く
練習がてらpythonで書いてみました．
プログラムは以下のようにして数独の解を求めています．

1. Encording : txtファイルの数独情報からcnfファイルを作る
2. Solving : システムコールでSATソルバー clasp を起動してcnfファイルを解く
3. Decording : clasp のログを解析して，数独の答えを表示する


GitHubに置きました．

https://github.com/okayu1230z/NumberPlaceSolver

数独の問題は，以下のようにファイルを作ってあげてください．

```:toi_001.txt
-,-,-,-,-,-,-,7,-
1,-,5,-,3,6,4,-,9
-,7,4,9,-,-,5,-,-
-,-,-,-,-,5,8,-,-
-,1,-,3,-,-,-,-,5
7,-,-,-,-,8,1,-,-
6,4,-,-,-,-,-,-,7
-,3,-,6,2,-,-,8,4
-,-,-,5,9,-,-,-,3
```

実行結果を以下に載せます．

プログラムを実行したら，解きたい問題ファイルの入力を求められますので，ファイル名を入力してください．

```:出力
$ python numpre_solver.py 
解きたい数独問題のパス : toi_001.txt
********************SAT-based Sudoku Solver********************
1. Encording
  square number plate problem : 3^2
  [pazzle problems]
    _____________________
    | - - - - - - - 7 - |
    | 1 - 5 - 3 6 4 - 9 |
    | - 7 4 9 - - 5 - - |
    | - - - - - 5 8 - - |
    | - 1 - 3 - - - - 5 |
    | 7 - - - - 8 1 - - |
    | 6 4 - - - - - - 7 |
    | - 3 - 6 2 - - 8 4 |
    | - - - 5 9 - - - 3 |
    _____________________

 2. Solving...
   solver :clasp
   solved!

 3. Decording
  Solving Time       : 0.001s

   [pazzle answer]
   _____________________
   | 9 6 2 4 5 1 3 7 8 |
   | 1 8 5 7 3 6 4 2 9 |
   | 3 7 4 9 8 2 5 6 1 |
   | 4 9 6 1 7 5 8 3 2 |
   | 2 1 8 3 6 9 7 4 5 |
   | 7 5 3 2 4 8 1 9 6 |
   | 6 4 9 8 1 3 2 5 7 |
   | 5 3 1 6 2 7 9 8 4 |
   | 8 2 7 5 9 4 6 1 3 |
   _____________________
***************************************************************
```

ここで出てきている Solving Time とは，SATソルバーがCNFを解いた時間です．

この問題では0.001秒なので，人間が解くより早いかもしれません．

[世界一難しい数独](https://rocketnews24.com/2012/07/03/22654/)は，0.006秒で解けました．

それ以外の問題で，問題集にある問題を800問ほど解きましたが，Solvingの平均は0.002秒切ってる感じでした．

中古の[ThinkPad X240](https://www.lenovo.com/jp/ja/static/catalog/nb-2015-x240_cf_0317)ですらそれくらいで解けるので，皆さんの手元のマシンの方がもう少し早いと思います．

ちなみに，制約プログラミング以外 (C/C++/Java/Python) だけで作られたソルバーでこれより早く解けてそうな記事は，自分の見た感じなかったので，あれば教えてください．

アルゴリズム書いていくよりも，SATソルバー使ったらとても早かったよ，って言うお話です．

### 16*16の数独
プログラムは一辺16マスの数独にも対応しています．

```:toi_002.txt
5,-,-,-,-,-,9,14,10,2,-,-,-,-,-,3
10,-,16,-,13,4,6,-,-,11,3,9,-,14,-,1
-,11,14,13,16,-,-,1,12,-,-,8,4,6,9,-
3,-,9,-,8,-,-,-,-,-,-,4,-,10,-,15
8,12,-,-,14,-,-,10,13,-,-,1,-,-,6,16
-,-,-,11,-,1,7,-,-,9,8,-,14,-,-,-
-,13,1,7,-,9,-,-,-,-,14,-,5,2,3,-
4,-,-,5,-,-,-,-,-,-,-,-,1,-,-,11
6,-,-,10,-,-,-,-,-,-,-,-,11,-,-,5
-,5,13,15,-,16,-,-,-,-,4,-,3,1,14,-
-,-,-,2,-,15,1,-,-,12,13,-,6,-,-,-
1,4,-,-,5,-,-,11,9,-,-,3,-,-,15,13
2,-,10,-,7,-,-,-,-,-,-,13,-,11,-,8
-,8,5,14,1,-,-,12,4,-,-,10,2,16,7,-
9,-,15,-,3,10,14,-,-,6,11,16,-,5,-,4
12,-,-,-,-,-,13,16,1,5,-,-,-,-,-,14
```
```:出力
$ python numpre_solver.py 
解きたい数独問題のパス : toi_002.txt
********************SAT-based Sudoku Solver********************
1. Encording
  square number plate problem : 4^2
  [pazzle problems]
    _____________________
    | 5 - - - - - 9 14 10 2 - - - - - 3 |
    | 10 - 16 - 13 4 6 - - 11 3 9 - 14 - 1 |
    | - 11 14 13 16 - - 1 12 - - 8 4 6 9 - |
    | 3 - 9 - 8 - - - - - - 4 - 10 - 15 |
    | 8 12 - - 14 - - 10 13 - - 1 - - 6 16 |
    | - - - 11 - 1 7 - - 9 8 - 14 - - - |
    | - 13 1 7 - 9 - - - - 14 - 5 2 3 - |
    | 4 - - 5 - - - - - - - - 1 - - 11 |
    | 6 - - 10 - - - - - - - - 11 - - 5 |
    | - 5 13 15 - 16 - - - - 4 - 3 1 14 - |
    | - - - 2 - 15 1 - - 12 13 - 6 - - - |
    | 1 4 - - 5 - - 11 9 - - 3 - - 15 13 |
    | 2 - 10 - 7 - - - - - - 13 - 11 - 8 |
    | - 8 5 14 1 - - 12 4 - - 10 2 16 7 - |
    | 9 - 15 - 3 10 14 - - 6 11 16 - 5 - 4 |
    | 12 - - - - - 13 16 1 5 - - - - - 14 |
    _____________________

 2. Solving...
   solver :clasp
   solved!

 3. Decording
  Solving Time       : 0.064s

   [pazzle answer]
   _____________________
   | 5 15 4 8 11 12 9 14 10 2 1 6 16 7 13 3 |
   | 10 2 16 12 13 4 6 15 7 11 3 9 8 14 5 1 |
   | 7 11 14 13 16 3 10 1 12 15 5 8 4 6 9 2 |
   | 3 1 9 6 8 2 5 7 14 13 16 4 12 10 11 15 |
   | 8 12 3 9 14 5 11 10 13 4 2 1 7 15 6 16 |
   | 16 10 6 11 2 1 7 3 5 9 8 15 14 13 4 12 |
   | 15 13 1 7 12 9 8 4 6 16 14 11 5 2 3 10 |
   | 4 14 2 5 15 13 16 6 3 7 10 12 1 9 8 11 |
   | 6 9 12 10 4 7 3 13 16 1 15 14 11 8 2 5 |
   | 11 5 13 15 6 16 12 8 2 10 4 7 3 1 14 9 |
   | 14 3 8 2 10 15 1 9 11 12 13 5 6 4 16 7 |
   | 1 4 7 16 5 14 2 11 9 8 6 3 10 12 15 13 |
   | 2 16 10 3 7 6 4 5 15 14 12 13 9 11 1 8 |
   | 13 8 5 14 1 11 15 12 4 3 9 10 2 16 7 6 |
   | 9 7 15 1 3 10 14 2 8 6 11 16 13 5 12 4 |
   | 12 6 11 4 9 8 13 16 1 5 7 2 15 3 10 14 |
   _____________________
***************************************************************

```
難しめの問題を選んでしまったのか，0.064秒もかかっていました．

もしかしたら，16*16の問題はこんなものかもしれません．

他のソルバーだと，どれくらいで解けるんでしょうね．

多分25*25の数独も解けます．(疲れたので解く気なし．)

# まとめと今後の予定
SATソルバーを知らない，使ったことがない人に向けて書いてみました．

アレも入れたい，コレも入れたいっていう欲求を抑えながら書いたので，本当の最低限しか書いてないと思います．

上手くいかない，ここ意味がわからないよ！ってことがあれば，教えてください．


ソルバーをこれ以上高速化する予定はありませんが，いくつか早くできそうな方法は知っているので，暇ができたら試してみます．

今は，このソルバーを使って，数独を解いてくれるアプリとか，サイトを立ち上げることに興味を持っています．

【追記　2020/01/12】
AWS + php + SATソルバー を用いて
9x9の数独を解いてくれるサイトを立ち上げました！
http://okmt1230z.com/sudoku/sudoku.html

[^1]: この記事に出てくる用語の説明などに関しましては，人工知能学会特集記事である [こちら](https://ci.nii.ac.jp/naid/110007504953) や Wikipedia に基づいています．
[^2]: この記事は情報処理学会の[SATとパズル](https://ipsj.ixsq.nii.ac.jp/ej/?action=pages_view_main&active_action=repository_view_main_item_detail&item_id=169443&item_no=1&page_id=13&block_id=8) を参考にしています．
[^3]: なんでも載ってるけど，研究室内で引用したら怒られるやつです．