yusuke-matsunaga / adc2019lite

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ADC2019 サンプルプログラムについて

松永 裕介

1. はじめに

まず,このファイルは markdown 形式で記述してあります. テキストファイルのままでも読めますが markdown 形式をサポートしているビュ ワーを使うとよりきれいに見えます.

このプログラムはDAシンポジウム2019アルゴリズムデザインコンテスト用に作っ たプログラムを一部簡略化してPython3で書き直したものです. もともと内部でSATソルバを用いているので小規模な問題しか解けませんのであしからず.

2. ファイル構成

  • README.md: このファイル

  • solver.py: 実際に問題を解くプログラム.要 Python3 とSATソルバのプログラム

  • viewer.py: 問題,および解答を表示するプログラム.要 Python3 + PyQt

  • core 問題ファイルのパーサーや問題や解答を表すクラスの定義ファイルを収めたディレクトリ ここのファイル群はSATソルバと無関係に利用可能です.

  • gui PyQt を用いて問題と解答のグラフィカルな表示を行うためのクラスの定義ファイルを収めたディレクトリ ここのファイル群は core 以下のファイルと PyQt に依存します.

  • sat SATソルバを用いて問題を解くプログラム ここでは python の subprocess パッケージを用いて外部コマンドを起動 しています. そのため,スタンドアロンで動作するSATソルバのプログラム(例えば MiniSat2 など) が必要です.

3. プログラムの使用方法

  • solver.py:

    使用方法: solver.py <問題ファイル名> <幅> <高さ> <SATプログラム名>

    solver.py はファイル名が示す通り Python のスクリプトファイルです. 実行には Python3 のインタープリタが必要です. UNIX環境での実行を考慮して先頭行に shebang (#!) で Python3 インタプリタを起動するようにしてあります.

    問題ファイルは ADC2019 の形式です.詳細はADC2019のwebページを参照してください.

    ADC2019は実際には配置配線の行える最小の矩形を求めるものですが,ここではあらかじめ盤面の幅と高さを指定して,その大きさの盤面内で配置,配線を試みます.

    最後の引数はSATソルバのプログラム名(実行パス)です.このPython3スクリプトは実際には問題をCNF形式のファイルを出力するだけでSAT問題を解きません. 代わりに外部のSATソルバを起動して解を求めます. そのため,実際にSAT問題を解くSATソルバを指定する必要があります. 具体的には MiniSat2(http://minisat.se/MiniSat.html )などが使用可能です.

    SATソルバが解を求めることができた場合には,その解の変数割り当てからADC2019の実際の解を作り出して標準出力に出力します. 形式はADC2019の解答のファイル形式です.

    SATソルバが解を求めることができなかった場合には UNSAT とだけ出力します.

  • viewer.py:

    使用方法: viewer.py [-a <解答ファイル名>] <問題ファイル名>

    viewer.py は ADC2019 の問題および解答をグラフィカルに表示するプログラムです. 内部で PyQt5(https://pypi.org/project/PyQt5/ )を用いています.

    引数として問題ファイルのみを与えた場合は問題のブロックを整列して表示します.

    -a オプションとともに解答ファイルも与えた場合には解答の配置配線結果を表示します.

4. SATソルバを用いたアルゴリズムの概要

ここではADC20198の問題をSATソルバを用いて解くアルゴリズムについて簡単に説明します.

4.1 用語の定義

  • 盤面: 配置配線を行う領域全体.なお,ここでは左上隅の座標を(0, 0)とし, X軸方向の正の方向を右,Y軸方向の正の方向を下とする.

  • グリッド: 盤面の基本単位のマス

  • ブロック: 配置対象の図形. ADC2019ではモノミノ(monomino)とテトロミノ(tetromino)を用いていますが本プログラムではブロックに関して何の制約も設けていません. ミノがグリッドに対応します.

  • ブロックの幅と高さ: そのブロックを囲む最小の矩形の幅と高さ

  • 線分番号: 配線で結ぶべき端子につけられた数字.ADC2019では1から始まる連続した整数となっている.

4.2 全体の戦略

問題のブロックを重なりなく盤面上に配置するための制約条件と, 同じ線分番号を持つブロック上の端子を結ぶための配線経路を求めるための制約条件を論理式として生成し,その論理式をSATソルバを用いて解きます. このように配置と配線を同時に解くやり方は, LSIの配置配線問題では規模が大きいのでまず用いられません. SATソルバを用いるとどの程度の規模の問題が解けるのか,ということを確認してみてください.

4.3 配置のための制約

まず,各ブロックの配置結果を表すための変数を用意します. ここでブロックの配置位置とはそのブロックを囲む最小の矩形の左上の座標位置を指すものとします. ブロックによってはその位置にミノを持たないものもあるので注意してください.

今,盤面の幅をW,高さをHとすると,ブロックの配置位置としてはX座標が0〜(W - 1), Y座標が0〜(H - 1)が考えられます.実際にはブロックの幅や高さが1以上の場合には, 右端や下端には置けないのでもう少し範囲が狭まりますが, ブロックごとに切り替えるのは面倒なのですべて上記の範囲としておきます.

もちろん,SATソルバはブール変数しか扱えませんので, ブロックの配置位置を表す整数値を複数のブール変数の組を用いて表す必要があります. これを「符号化」と呼びます. 符号化には様々なやり方がありますが,ここではワンホット(one-hot)符号化を用います.

ワンホット符号化ではN種類を値を表すのにN個のブール変数を用います. 値0, 1, ..., N - 1に対応する変数をそれぞれX_0, X_1, ..., X_(N - 1)とします. もしも値が i (0 <= i < N) だった場合には X_i のみが 1 となり, それ以外の変数はすべて 0 となります. これがワンホット符号化です.

ワンホット符号化ではその名の通り,1となっている変数の数が常に一つですので, その条件を表す制約式を追加する必要があります. うまくやるとこの制約式は N log N のオーダーで作ることができますが, 単純なやり方ではN個のなかから2つの組を全て列挙して(X_aとX_bとする), その2つが同時に1にならない制約式を追加します(~X_a or ~X_b). この場合,追加される制約式の数はN(N - 1)/2となります. Nが大きい数の場合 N log N のオーダーの制約式の生成を考えてみてください.

さて,前述のように,幅や高さが1以上のブロックの場合, 右端や下端に置けないのでその部分を禁止する制約を考える必要があります. これは単純にその座標位置を表す変数の否定を追加します. 例えば盤面の幅が10でブロックの幅が2の場合, X座標が9の位置にはブロックを配置することはできません. そこで ~X_9 という論理式を追加しておきます. これでX_9はは1になりません.

正しい配置となるためには, さらに異なるブロックが盤面上の同じグリッドで重ならない, という条件が必要です. これに対する制約式の作り方もいろいろと考えられますが, ここでは最も単純と思われるものを紹介します.

そのために,盤面の各グリッドに対して,自分を使っているブロック番号を表す変数を用意します. ブロック番号も2値ではないので複数のブール変数を用いて表す必要があります. ここではこれもワンホット符号化を用いるものとします. ただし,どのブロックにも使われていないグリッドもありますので, 正確にはat-most-one符号化となります. 具体的にはワンホット符号化の条件のひとつである 「どれか一つの変数が1となる」という条件を外します. (x, y)の位置のグリッドがあるブロックに使われている場合, ブロックの配置位置(x_1, y_1)と(x, y)の差がそのブロックの 基準位置とブロック内のいずれかミノの相対位置と等しいことを意味します. この性質をそのまま制約式の形に変換しておきます.

以上で配置条件を表す論理式ができます.

4.4 配線のための制約

端子位置が固定されている場合はADC2016などと同様の問題となりますので, 詳しくはそちらを参考にしてください.

ここではブロックの配置が決まっていないため,配線問題も端子位置が可変となります. そのため,いくつかの変数を用意します.すべてグリッド毎に考えます.

  • b_var: そのグリッドがいずれかのブロックに用いられている時1となる.

  • t_var: そのグリッドがいずれかのブロックの端子として用いられている時1 となる.t_varが1のときb_varも1となっている.

  • l_var: そのグリッドが配線として用いられている場合,線分番号を表す. 端子の場合もl_varに線分番号を持つ.それ以外では0とする. これは2値ではありませんのでat-most-one符号化を用います. 0用の変数は作りません.

まず,b_var が1となる条件ですが, これは配置のための制約式で使った各グリッドを使用しているブロック番号を表す変数から作ることができます.

t_var は少し面倒ですが,各ブロックの配置位置を表す変数とグリッドの座標の関係から作ることができます.

b_var と t_var を用いると,配線問題として各グリッドを次の3種類に分けることができます.

  • [端子グリッド] t_var : 端子が置かれているグリッド

  • [ブロックグリッド] ~t_var and b_var : 端子の置かれていないブロック

  • [配線グリッド] ~b_var : 配線に用いることの出来るグリッド

もちろん,ブロックグリッドではl_varは0となります. 端子グリッドではその端子の線分番号に応じた変数が1となります. 配線グリッドでは周囲の状況に応じて変数の値が決まります.

配線グリッドでは隣接するグリッドとの間に結線があるかどうかで値が変わります. そのため隣接したグリッド間に結線がある時に1となる変数を全てのグリッド間に用意します. ここで,グリッドとグリッドの間に「枝」が張られていると考えます. これはグラフ理論で用いられる用語です. その枝の変数が1の時に枝の両端のグリッドを結ぶ配線があるものと考えます. すると,配線で結ばれたグリッドのl_varは等しくならなければなりません. これが枝とグリッドに関する制約です.

枝の変数は自由に1と0の値をとってよいわけではなく, 全体として配線経路となるために以下の様な制約があります.

  • (x, y) のグリッドが端子グリッドの場合: (x, y) に接続する枝のうちただ1つの枝のみ1となる.
  • (x, y) のグリッドがブロックグリッドの場合: (x, y) に接続するすべての枝は0となる.
  • (x, y) のグリッドが配線グリッドの場合: (x, y) に接続する枝のうち0個か2個が1となる.

最後の「1となる変数が0個か2個」という制約はひっくり返して考えると, 「1となる変数の数が1個と3個以上となることはない」, となりますので,そのような組み合わせを列挙してド・モルガンの法則でひっくり返します.

配線経路としてはさら「コの字」経路の禁止などを加えておくとよいでしょう. この辺の話はADC2016を参照してください.

4.5 解の生成

以上の論理式をMiniSat2の入力形式であるDIMACS形式で出力しMiniSat2を起動すれば解が求まります. ただし,MiniSat2が返すのは各変数の値割り当てですのでそこから配置配線結果に変換する必要があります.

まず,配置結果は簡単です.各ブロックのX座標とY座標を表す変数のなかから1となっている変数を探し, その値をブロックの配置位置とします.

すこし面倒なのが配線結果です. 実は上記の符号化では完全な配線結果を求めることはできません. より正確には,実際の配線に用いられていないのに l_var の値が非0になってしまうことがあります. ただしい配線上のl_varの値は間違っていないので, 使われていないグリッド上のl_varの値を補正する必要があります. そのために,配置結果から求められた端子位置から同じ線分番号を持つグリッドをたどって配線経路を確定します.

4.6 プログラムの改良

以上のプログラムで ADC2019 のサンプル問題 sample_Q.txt は正しく解けることを確認しています. しかし,いろいろと改良すべきポイントはあります.

まず,このプログラムではあらかじめ盤面サイズを与える必要があり, 自動的に面積最小の配置配線結果を得ることができません. そのため,このプログラムを用いる上位のアルゴリズムを考える必要があります.

制約条件の生成に関して,ここでは全ての符号化をワンホット(もしくはat-most-one)符号化で行っています. 実際には2進符号化や順序制約符号化など他の符号化も考えられます. 問題の性質をよく考えて効率的な符号化を考えてください.

何度もSATソルバを起動したり,既存の制約式に徐々に制約式を加えてその都度解を求める場合, 外部プログラムを呼び出す形式は効率的ではありません. そのため,Python3スクリプト内部でSATソルバを呼び出す方式が望ましいのですが, Python3 でSATソルバを記述してもろくな実行性能はでないので, c++ などで記述されたSATソルバのライブラリをPython3から呼び出す必要があります. 私はcython(https://cython.org/ )を用いた自作SATソルバのPython3ラッパを使っています.

About


Languages

Language:Python 100.0%