graphillion - 莫大な数の部分 グラフを扱う Python

今日の内容
graphillion - 莫大な数の部分
グラフを扱う Python ライブラリ
• graphillion ライブラリの紹介
• graphillion ライブラリでできること
• graphillion の内部データ構造
• ZDD
奈良先端科学技術大学院大学
川原 純
• graphillion が使用しているアルゴリズム
• フロンティア法
• 適用事例紹介 (1), (2), (3)
謝辞
本スライドで使用している図の一部と全適用事例は発売予定の書籍
「超高速グラフ列挙アルゴリズム」(仮題)
のドラフト版から引用しています。図の引用は「ZDD書籍から引用」と明示しています。
graphillion ライブラリの作者 井上 武 さん,岩下 洋哲 さん
書籍の著者 湊 真一 先生,斎藤 寿樹 先生,安田 宜仁さん,井上 武 さん,
羽室 行信 先生,前川 浩基 さん,丸橋 弘明 さん,堀山 貴史 先生,その他の著者の皆様,
JST ERATO 湊離散構造処理系プロジェクトメンバーの皆様に感謝いたします。
graphillion とは
2
graphillion とは
http://graphillion.org
NTT研究所 井上 武 氏 作
http://graphillion.org
NTT研究所 井上 武 氏 作
• 部分グラフ列挙ツール
• グラフに関する様々な最適化問題を解く
• 部分グラフ列挙ツール
• グラフに関する様々な最適化問題を解く
グラフとは
頂点
辺
グラフ
路線図
地図(の隣接関係)
3
知り合い関係
「もの」と、「もの」のつながりを
抽象化して表したもの
4
部分グラフ
部分グラフ列挙
• 与えられたグラフの一部分からなるグラフ
• 与えられた条件を満たす部分グラフをすべて求める
• 例:
s
与えられたグラフ
s から t までの、
同じ頂点を通らない経路は?
部分グラフの例
• 経路(パス)や全域木などは部分グラフ
t
t
s
全域木
s-t 経路(s-t パス)
5
6
部分グラフ列挙
部分グラフ列挙
• 与えられた条件を満たす部分グラフをすべて求める
• 例:
全体グラフ (universe)
• 与えられた条件を満たす部分グラフをすべて求める
• 例:
s
部分グラフ
s から t までの、
同じ頂点を通らない経路は?
列挙して何が嬉しいのか?
後述
全体グラフ (universe)
s
t
t
7
8
graphillion を使ってみよう(1)
graphillion を使ってみよう(2)
• インストール (Mac, Linux)
• グラフの表現
全体グラフ (universe)
• networkx ライブラリと同じ
または、
s=1
2
3
4
5
6
7
8
http://graphillion.org
$ easy_install graphillion
からダウンロード、ビルド
• python インタプリタを起動
$ python
• ライブラリのインポート
頂点: オブジェクト(何でもよい)
本講演では1,2,3,...
t=9
辺: 頂点のタプル
(1, 2) や (2, 3) など
グラフ: 辺のリスト
[(1, 2), (1, 4), (2, 3), (2, 5), (3, 6), (4, 5), (4, 7),
(5, 6), (5, 8), (6, 9), (7, 8), (8, 9)]
• 全体グラフの設定
>>> from graphillion import GraphSet
>>> GraphSet.set_universe( [(1, 2), (1, 4), (2, 3), (2, 5),
(3, 6), (4, 5), (4, 7),(5, 6), (5, 8), (6, 9), (7, 8), (8, 9)] )
9
graphillion を使ってみよう(3)
• s-t 経路を列挙する
graphillion でできること(1)
全体グラフ (universe)
s=1 2 3
4
5
7
8
>>> paths = GraphSet.paths(1, 9)
10
• 数のカウント
6
>>> print paths.len()
12
• 各部分グラフに対する処理
t=9
>>> for p in paths:
...
print p
引数に始点と終点
全体グラフ (universe)
3
2
s=1
4
5
7
8
6
t=9
paths 変数に、部分グラフの集合が格納される
11
12
graphillion でできること(1)
• 数のカウント
>>> print paths.len()
12
• 各部分グラフに対する処理
全体グラフ (universe)
3
2
s=1
4
5
7
8
2),
2),
2),
2),
2),
2),
4),
4),
4),
4),
4),
4),
(2,
(2,
(2,
(2,
(2,
(2,
(2,
(2,
(4,
(4,
(4,
(4,
3),
3),
3),
5),
5),
5),
3),
3),
5),
5),
7),
7),
(3,
(3,
(3,
(4,
(5,
(5,
(2,
(2,
(5,
(5,
(5,
(7,
6),
6),
6),
5),
6),
8),
5),
5),
6),
8),
6),
8),
(4,
(5,
(6,
(4,
(6,
(8,
(3,
(3,
(6,
(8,
(5,
(8,
5),
6),
9)]
7),
9)]
9)]
6),
6),
9)]
9)]
8),
9)]
• 経路の短い順に、各部分グラフを処理
6
t=9
>>> for p in paths:
...
print p
...
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
(universe)
graphillion でできること(2)s =全体グラフ
1 2 3
(7, 8), (8, 9)]
(4, 5), (6, 9)]
(4, 7), (5, 8), (6, 9), (7, 8)]
5
7
8
6
t=9
>>> for p in paths.min_iter():
...
print p
...
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
[(1,
(4, 7), (5, 6), (7, 8), (8, 9)]
(5, 8), (8, 9)]
4
4),
4),
4),
2),
2),
2),
4),
4),
2),
2),
4),
2),
(4,
(4,
(4,
(2,
(2,
(2,
(4,
(2,
(2,
(2,
(2,
(2,
7),
5),
5),
5),
5),
3),
7),
3),
5),
3),
3),
3),
(7,
(5,
(5,
(5,
(5,
(3,
(5,
(2,
(4,
(3,
(2,
(3,
8),
8),
6),
8),
6),
6),
6),
5),
5),
6),
5),
6),
(8,
(8,
(6,
(8,
(6,
(6,
(5,
(3,
(4,
(5,
(3,
(4,
9)]
9)]
9)]
9)]
9)]
9)]
8),
6),
7),
6),
6),
5),
(6,
(4,
(7,
(5,
(4,
(4,
9),
5),
8),
8),
7),
7),
(7,
(6,
(8,
(8,
(5,
(5,
8)]
9)]
9)]
9)]
8), (6, 9), (7, 8)]
6), (7, 8), (8, 9)]
(6, 9), (7, 8)]
13
14
(universe)
graphillion でできること(3)s =全体グラフ
1 2 3
• (一様)ランダムに1つ抽出
>>> p = paths.choice()
>>> print p
4
5
7
8
6
t=9
graphillion でできること(4)
• さらに、頂点3を通らない
経路のみを抽出
>>> paths3 = paths2.excluding(3)
[(1, 2), (2, 3), (3, 6), (4, 5), (4, 7), (5, 6), (7, 8), (8, 9)]
全体グラフ (universe)
3
2
s=1
4
5
7
8
6
t=9
• 頂点5を通る経路のみを抽出
>>> paths2 = paths.including(5)
15
16
1.7
graphillion でできること(5)
• 辺に重みをつけて、重みの和が
最大・最小の部分グラフを求める
3.2
...
全体グラフ (universe)
s=1 2 3
>>> weights = {}
>>> weights[(1, 2)] = 3.2
>>> weights[(1, 4)] = 1.7
# (略)
>>> weights[(8, 9)] = 6.2
>>>
>>> iter = paths3.max_iter(weights)
>>> p1 = iter.next()
>>> p2 = iter.next()
4
5
7
8
graphillion の威力(1)
データ取得: 駅データ.jp
http://ekidata.jp
• JR(Japan Railway)の路線図
6
頂点数: 4534, 辺数: 4646
t=9
6.2
一番長い経路
二番目に長い経路
辺の重みは、辺をキー、重みをバリューとするディクショナリで与える
最北駅(稚内)~最南駅(西大山) の経路
>>> GraphSet.set_universe(jrmap)
>>> weights = read_weights()
>>>
>>> paths = GraphSet.paths(“1111553”, “1193021”)
>>> paths.len()
1112870539692503649611518720
計算時間: 258.16 秒
Intel Xeon E5-1620 3.60GHz
使用メモリ: 12.4 GB
経路は圧縮して保持されている
17
18
ZDD書籍から引用
graphillion の威力(2)
graphillion で扱える部分グラフの種類
• 経路
• JR(Japan Railway)の路線図
頂点数: 4534, 辺数: 4646
• s-t 経路
• ハミルトン経路(すべての頂点を通る)
• サイクル(1周して戻る)
最北駅(稚内)~最南駅(西大山) の経路
最長経路を求める
>>> max_path = paths.max_iter(weights).next()
>>> print len(max_path)
2650
>>> sum = 0.0
>>> for edge in max_path:
...
sum += weights[edge]
...
>>> print sum
10337.3
•木
駅数 ― 1
• 森、全域森
• 木、全域木
• 連結成分
• マッチング
• クリーク(頂点同士がすべて結ばれている頂点集合)
総距離
最短経路も求まるが、ダイクストラ法などの既存アルゴリズムの方が速い
19
ZDD書籍から引用
• その他、様々な制約を課した部分グラフ
20
今日の内容
graphillion で用いられるデータ構造
ゼロサプレス型二分決定グラフ
• graphillion ライブラリの紹介
• Zero-suppressed Binary Decision Diagram (ZDD) [S.Minato 93]
• 集合族(集合の集合)をコンパクトに効率良く記憶
• graphillion ライブラリでできること
• graphillion の内部データ構造
• ZDD
• graphillion が使用しているアルゴリズム
特殊な形をしたグラフ
• フロンティア法
• 適用事例紹介 (1), (2), (3)
{e1, e2, e5}, {e1, e3, e4, e7}, {e1, e5, e8, e12},
{e3, e7, e9}, {e2, e6, e7, e8, e9, e10}, {e4},
{e1, e4, e6, e8}, {e1, e2, e5, e8}, {e1, e9, e10},
{e4, e5, e9}, {e1, e3, e4, e5, e9, e11}, {e6, e7},
{e1, e4, e5}, {e1, e5, e8, e10}, {e2, e5, e11},
{e5, e8, e9}, {e2, e3, e7, e8}, {e10, e11, e12},
…
ZDD
21
22
ZDD の基本
ZDD の基本
root
e1
0
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 }
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 } }
0
1
e2
e3
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 }
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 } }
e3
e3
e3
e4
e5
0
1
e2
e4
集合族
e1
0 : 0 終端 (0-terminal)
1 : 1 終端 (1-terminal)
1
ei
ZDD
: ノード
それぞれ
1つずつもつ
e5
e1 ~ e5 いずれかのラベル
e1 , e2,…, e5 の順序
0
1
ZDD
要素は {e1, e2 , e3 , e4, e5} の5種類で固定
(事前に固定する必要あり)
23
24
ZDD の基本
ZDD の基本
root
ノードは0枝と1枝を1つずつもつ
e1
0
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 }
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 } }
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 }
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 } }
e3
1 : 1 終端 (1-terminal)
ei
: ノード
それぞれ
1つずつもつ
e3
e3
e4
e5
e5
0
e1 ~ e5 いずれかのラベル
e1 , e2,…, e5 の順序
1
e2
e4
0 : 0 終端 (0-terminal)
e1
0
1
e2
e3
root
ノードは0枝と1枝を1つずつもつ
1つの集合が、
root から 1 までの1本のパスに対応
1
0
ZDD
1
ZDD
0
: 0-terminal
1
: 1-terminal
ei
: node with label
e1
~
e5
25
ZDD の基本
26
ZDD の基本
root
ノードは0枝と1枝を1つずつもつ
e1
0
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 }
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 } }
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 }
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 } }
e3
e3
e5
1つの集合が、
root から 1 までの1本のパスに対応
1
ZDD
0
: 0-terminal
1
: 1-terminal
ei
: node with label
e1
~
e3
e4
e5
0
1
e2
e4
1つの集合が、
root から 1 までの1本のパスに対応
e1
0
1
e2
e3
root
ノードは0枝と1枝を1つずつもつ
0
1
ZDD
e5
27
0
: 0-terminal
1
: 1-terminal
ei
: node with label
e1
~
e5
28
ZDD の基本
ZDD の基本
ノード共有ルール
ZDD は2分木を圧縮したものとみなすことができる
{ { e1 e3 } , { e2 e5 } , { e2 e4 e5 },
}
{ e3 e4 e5 } , { e3 e5 } ,
{ e5 }
e1 1
0
e1 = 0 e1 e1 = 1
e2
e2
e2
e2 = 1 e2 = 0
e2 = 1
e2 = 0
e3
e3
e3
e3
e3
e3
1
0
e4
e4
e5
...
...
0
1 1 1 1
1
0
1
ゼロサプレスルール
2種類のルールを
繰り返し適用して圧縮
e5
0
他は 0
...
2つの「等価な」ノードは
共有される
ei
...
1
1
...
...
...
...
1
ZDD
{ e5 } { e3 e5 }
0
0
1
1
1-枝が0-Terminalを指す
ノードは削除される
29
30
今日の内容
ZDDの演算
• ZDD を用いると、集合演算や、要素の検索等、様々な演算が
効率良く行える
• graphillion ライブラリでできること
• graphillion の内部データ構造
和集合を求める
(union)
∪
• graphillion ライブラリの紹介
• ZDD
• graphillion が使用しているアルゴリズム
• フロンティア法
{e1, e2, e5}, {e1, e4},
{e1, e2, e5},
{e1, e3, e4, e7} {e1, e3, e4, e7} {e1, e4}
{e1, e3, e4, e7}
• 適用事例紹介 (1), (2), (3)
s-t 経路を例に説明
要素の抽出
{e1, e2, e5},
{e1, e4}
{e1, e3, e4, e7}
{e1, e4}
{e1, e3, e4, e7}
e4 を含む
集合を取り出す
31
アルゴリズムは省略
32
s-t 経路は辺の集合で表現できる
s-t 経路は辺の集合で表現できる
s
e1
e4
e3
e2
s
e1
e4
e3
e2
s
t
e4
e1
s
e3
e2
e3
s
{e1, e3 ,e5}
e5
e4
{e1, e4}
t
e1
e4
s
t
e1
e3
{e2, e5}
e2
e5
e1
e4
e3
e2
e4
e2
t
s
e4
e3
e2
e5
辺集合の集合で表す
e5
e1
s
e3
s
t
{e1, e3 ,e5}
e5
e4
{e1, e4}
e5
e1
{e2, e3 ,e4}
t
全ての s-t 経路を列挙して、
t
e3
e2
e5
e5
e1
s
t
t
{e2, e5}
e2
e5
e1
e4
e3
e2
t
{e2, e3 ,e4}
e5
33
s-t 経路は辺の集合で表現できる
s
e1
e4
e2
s-t 経路は辺の集合で表現できる
全ての s-t 経路を列挙して、
t
e3
34
s
辺集合の集合で表す
e5
e1
e4
e3
e2
s
s
e4
e3
e5
e1
s
t
{e1, e3 ,e5}
e4
e3
{e1, e4}
e5
e1
e2
t
e5
e1
e4
e3
e2
e1
e4
e3
{e2, e5}
e2
e2
t
s
e4
e3
e2
35
辺集合の集合で表す
これをZDDで表現する
e5
t
s
e5
e1
s
t
{e1, e3 ,e5}
e4
e3
{e1, e4}
e5
e1
{e2, e3 ,e4}
e5
全ての s-t 経路を列挙して、
t
全 s-t 経路の集合 = {{e1, e4}, {e2, e5},{e1, e3 ,e5},{e2, e3 ,e4}}
s
t
e4
e3
e2
全 s-t 経路の集合 = {{e1, e4}, {e2, e5},{e1, e3 ,e5},{e2, e3 ,e4}}
s
e1
{e2, e5}
e2
e5
e1
e4
e3
e2
t
t
{e2, e3 ,e4}
e5
36
経路列挙(ZDD構築)アルゴリズム [Knuth 08]
経路列挙(ZDD構築)アルゴリズム [Knuth 08]
e1
s
e4
t
e3
e2
e5
1. 辺に順番を付ける
(例えば、s から幅優先)
e1 = 0 e1 e1 = 1
e2
e2
e2 = 1 e2 = 0
e2 = 1
e2 = 0
e3
e3
e3
e3
1
0
(もっと良い方法もあり)
2. ZDDを構築
s-t path
e
s 1
辺 e1, e2,… の順に処理
各辺変数 ei に対し、
ei = 0 or 1 を決めていく
e2
e1
s
t
e3
e2
e5
e4
1. 辺に順番を付ける
(例えば、s から幅優先)
e5
(もっと良い方法もあり)
2. ZDDを構築
1
e4
e3
辺 e1, e2,… の順に処理
t
各辺変数 ei に対し、
ei = 0 or 1 を決めていく
e5
ei = 1 である辺が s-t 経路になっているか?
e1 = 0 e1 e1 = 1
e2
e2
e2 = 1 e2 = 0
e2 = 1
e2 = 0
e3
e3
e3
e3
1
0
e5
s-t経路でない
s
e1
0
e4
e3
e2
s-t path +
余分な辺
s
t
経路列挙(ZDD構築)アルゴリズム [Knuth 08]
t
e3
e2
e5
1. 辺に順番を付ける
(例えば、s から幅優先)
e1 = 0 e1 e1 = 1
e2
e2
e2 = 1 e2 = 0
e2 = 1
e2 = 0
e3
e3
e3
e3
1
0
(もっと良い方法もあり)
2. ZDDを構築
e1
e4
e3
e2
e5
s
e4
0
e5
a
e1
e2
e5
t
e3
e6
b e
4
c
e4
e5
1
1
1 1 他は0
辺 e1, e2,… の順に処理
各辺変数 ei に対し、
ei = 0 or 1 を決めていく
…
39
t
38
経路列挙(ZDD構築)アルゴリズム [Knuth 08]
e1
e4
ei = 1 である辺が s-t 経路になっているか?
37
s
e4
40
経路列挙(ZDD構築)アルゴリズム [Knuth 08]
s
a
e1
経路列挙(ZDD構築)アルゴリズム [Knuth 08]
e5
t
e3
e2
処理済み辺
未処理辺
e6
b e
4
f
c
f
d
d
a
s
b
t
f
c
s
b
h
g
d
d
a
s
t
f
c
s
h
g
フロンティア
部分経路の反対側の端点を記憶しておき、
フロンティア上ですべて一致するなら、
2つは同じ状態とみなす。
…
41
42
6
6
e1
e3
s
e2 e e4 e e5
8
6
6
6
e7
2
4
3
3
2
4
3
3
4
3
3
経路の数え上げ
12
ZDDの形式で保存
• ZDDが構築できれば、経路の全列挙は簡単
• top から 1-終端までたどればよい
• 構築したZDDはコンパクトなので、そのまま保持可能
• ZDD演算を行うことで、経路の条件付き検索が可能
{e1, e2, e5},
{e1, e4}
{e1, e3, e4, e7}
{e1, e4}
{e1,e3, e4, e7}
e4 を通る
経路のみを
取り出す
2
1
1
2
1
1
2
1
1
1
1
1
1
2
1
1
1
1
43
2
1
2
1
1
1
1
1
1
e11
2 1
2
1
1
e9
e10
e12 t
1
1
重み付き和の最大値・最小値も
同様の方法で行える
44
一様ランダムサンプリング
確率
6
13
13
0 a 1
6
b
b
3
7
13
d
2
6
7
b
b
b
b
3
3
6
7
4
3
c
c
c
1
{d}, {c}, {c, d},
{b}, {b, d}, {b, c, d},
{a}, {a, d}, {a, c, d},
{a, b}, {a, b, c}, {a, b, d},
{a, b, c, d}
1
確率
0 a 1
c
d
0
確率
7
13
4
c
1
13
6
3
c
a
一様ランダムサンプリング
d
d
0
1
2
b
6
確率
3
6
3
3
c
{d}, {c}, {c, d},
{b}, {b, d}, {b, c, d},
{a}, {a, d}, {a, c, d},
{a, b}, {a, b, c}, {a, b, d},
{a, b, c, d}
45
一様ランダムサンプリング
graphillion で扱える部分グラフの種類
(再掲)
• s-t 経路
• ハミルトン経路(すべての頂点を通る)
• サイクル(1周して戻る)
13
0 a 1
6
b
b
7
c
c
c
d
d
0
•木
4
3
1
46
• 経路
{b, d}
3
c
1
2
• 森、全域森
• 木、全域木
• 連結成分
{d}, {c}, {c, d},
{b}, {b, d}, {b, c, d},
{a}, {a, d}, {a, c, d},
{a, b}, {a, b, c}, {a, b, d},
{a, b, c, d}
フロンティア法は、Knuth のアルゴリズム(s-t 経路)を、
様々な部分グラフに適用できるように拡張したもの
• マッチング
• クリーク(頂点同士がすべて結ばれている頂点集合)
• その他、様々な制約を課した部分グラフ
47
48
適用事例1: パズルソルバー
適用事例1-1: ナンバーリンクソルバー 1/4
• ナンバーリンク
• グラフの問題として定式化
s1-t1 経路
s2-t2 経路
...
sp-tp 経路
(複数終端対経路)
を同時に求める問題
同じ数字同士を、線を交差させずに結ぶ
R. Yoshinaka, T. Saitoh, J. Kawahara, K. Tsuruma, H. Iwashita, S. Minato.
Finding all solutions and instances of numberlink and slitherlink by ZDDs.
Algorithms, 5, 176–213, 2012.
49
50
ZDD書籍から引用
ZDD書籍から引用
適用事例1-1: ナンバーリンクソルバー 2/4
適用事例1-1: ナンバーリンクソルバー 3/4
• graphillion には、直接、複数終端対
経路を求めるメソッドはない
• 条件をつけた部分グラフを求める
メソッドを使う
• 次数の条件
• 終端(si, ti)の次数は1
• それ以外の次数は0 または 2
>>>
>>>
>>>
>>>
>>>
>>>
...
>>>
...
universe = [...]
GraphSet.set_universe(universe)
point_pairs = [[2, 6], [1, 11], [3, 22]]
points = sum(point_pairs, [])
[2, 6, 1, 11, 3, 22]
deg_cons = dict([(v, 1) for v in
set(range(1, 37)) if v in points])
deg_cons.update(dict([(v, (0, 2)) for v in
set(range(1, 37)) if v not in points]))
{1: 1, 2: 1, 3: 1, 4: (0, 2), 5: (0, 2), 6: 1,...
51
ZDD書籍から引用
52
ZDD書籍から引用
適用事例1-1: ナンバーリンクソルバー 4/4
適用事例1-2: スリザーリンクソルバー 1/4
[[2, 6], [1, 11], [3, 22]]
s2
s1
s3
書かれている数字の
本数の辺を使う
• スリザーリンク
>>> solutions = GraphSet.graphs(
...
vertex_groups=point_pairs,
...
degree_constraints=deg_cons,
...
no_loop=True)
>>>
>>> print solutions.len()
1
t1
t2
サイクルを1個作る
t3
得られた解
{1: 1, 2: 1, 3: 1, 4: (0, 2), 5: (0, 2), 6: 1,...
53
54
ZDD書籍から引用
ZDD書籍から引用
適用事例1-2: スリザーリンクソルバー 2/4
適用事例1-2: スリザーリンクソルバー 3/4
• 初めに、マスの数字は無視して、サイクルをすべ
て求める
• 次に、ある1つのマスに着目
そのマスの制約を満たしている部分グラフのみを抽出
>>> universe = [...]
>>> GraphSet.set_universe(universe)
>>>
>>> cycles = GraphSet.cycles()
>>>
>>>
>>>
>>>
e1
e2
e3
e4
=
=
=
=
(15,
(15,
(16,
(23,
16)
23)
24)
24)
15 e1 16
e2 e3
23
24
e4
>>> from itertools import
combinations
>>> g_set = list(combinations([e1,
e2, e3, e4], 2))
>>> in_gs = GraphSet(g_set)
>>> cycles = cycles.including(in_gs)
55
56
ZDD書籍から引用
適用事例2: 配電網のスイッチ構成 1/10
適用事例1-2: スリザーリンクソルバー 4/4
• 次に、ある1つのマスに着目
そのマスの制約を満たしている部分グラフのみを抽出
>>>
e2,
>>>
>>>
g_set2 = list(combinations([e1,
e3, e4], 3))
ex_gs = GraphSet(g_set2)
cycles = cycles.excluding(ex_gs)
配電網
すべての家は
ちょうど1つの変電所に
つながっている必要がある
15 e1 16
e2 e3
23
24
e4
サイクルが生じてはいけない
変電所を根とする根付き全域森
これをすべてのマスについて行う。
得られた部分グラフが解
2
0
引用: http://www.jst.go.jp/pr/announce/20120223/index.html
2
0
0
2
得られた解
0
2
57
ZDD書籍から引用
T. Inoue et al., “Distribution Loss Minimization with Guaranteed Error Bound,”
IEEE Transactions on Smart Grid, Vol. 5, Issue 1, 2014, pp. 102-111.
適用事例2: 配電網のスイッチ構成 2/10
適用事例2: 配電網のスイッチ構成 3/10
• 根付き全域森を列挙する
• 電気制約
58
• 1つの変電所が給電可能な最大家庭数に制限がある
>>> universe = [...]
>>> GraphSet.set_universe(universe)
>>> forests = GraphSet.forests(roots=[1, 9, 73, 81],
is_spanning=True)
>>> print forests.len()
54060425088
制限数 5
...
59
制限数オーバー
60
適用事例2: 配電網のスイッチ構成 5/10
適用事例2: 配電網のスイッチ構成 4/10
• 電気制約を満たす根付き全域森を求める
• 電気制約を満たす根付き全域森を求める
制限数 6
制限数 6
...
...
まずは、電気制約を満たさない大きな木を求める
根付き全域森の集合から、電気制約に違反する
木を含む根付き全域森を削除
>>> safe_forests = forests.excluding(too_large_trees)
>>> all_trees = GraphSet.trees()
>>> too_large_trees = all_trees.larger(6)
...
...
61
62
適用事例2: 配電網のスイッチ構成 6/10
適用事例2: 配電網のスイッチ構成 7/10
• 与えられた構成(根付き全域森)が、制約を満たす
かどうか調べる
• 与えられた構成から、スイッチON/OFFをいくつか
変化させて、制約を満たす構成に変化させる
>>> unsafe_forest = [...]
>>> unsafe_forest in safe_forests
False
2回
5回
...
4回
与えられた構成
変化数最小の構成を求めるには?
63
safe_forests (制約を満たす)
64
適用事例2: 配電網のスイッチ構成 8/10
適用事例2: 配電網のスイッチ構成 9/10
• 最適化問題として定式化
• 与えられた構成(根付き全域森)が、制約を満たす
かどうか調べる
スイッチを変化させるごとにペナルティーが発生
ペナルティーを最小化する構成を求める
graphillion では、採用した辺にしか重みをつけることはできない
-1
+1 -1
+1 -1 +1 -1
+1
+1
+1 +1
-1
+1 +1
-1
-1
-1
-1
与えられた構成
(元グラフ)
元グラフにない辺を採用すると
ペナルティー +1 点 (採用しないと 0点)
元グラフにある辺を採用すると
65 0点)
ペナルティー -1 点 (採用しないと
>>>
>>>
>>>
>>>
...
...
>>>
...
...
...
unsafe_forest = [...]
weights = {}
for switch in universe:
weights[switch] = 1 if switch in unsafe_forest else -1
for f in safe_forests.min_iter(weights):
print f
on_switches = [e for e in f if e not in unsafe_forest]
off_switches = [e for e in unsafe_forest if e not in f]
66
適用事例2: 配電網のスイッチ構成 10/10
事例3: 避難所割り当て 1/2
• blocking set (hitting set)
• グラフと避難所が与えられたとき、避難所ごとにグ
ラフを分割
切断によって、構成(根付き全域森)が
なくなるような辺の集合
>>> failures = safe_forests.blocking()
>>> minimal_failures = failures.minimal()
極小のものだけを求めることができる
67
68
事例3: 避難所割り当て 2/2
その他の適用事例
• 割当をすべて列挙する
• 選挙区割りの列挙
• 多面体の展開図の列挙
• 住宅フロアプランの列挙
• ...
6
1, 6, 10 は同じ連結成分に
含まれてはいけない
1
10
>>>
>>>
>>>
>>>
>>>
universe = [...]
GraphSet.set_universe(universe)
assignments = GraphSet.graphs([[1], [6], [10]])
maximal_assignments = assignments.maximal()
同じ連結成分内で辺が張れる場合は
必ず張る
69
まとめ
• graphillion の紹介
• 内部データ構造とアルゴリズム
• ZDD
• フロンティア法
• 適用事例の紹介
• パズルソルバー(ナンバーリンク、スリザーリンク)
• 配電網のスイッチ構成
• 避難所割り当て
71
70