Directed Scale-Free Graphsによるグラフの生成

Webリンクなどは有向グラフとよばれるグラフとなっており,リンクに向きがあります.Web用のソフトウェアを作成したとき,シミュレーション等を行うために疑似データを生成したくなりますが,"Directed scale-free graphs", B. Bollobas, C. Borgs and O. Riordan, Proceedings of the 14th Annual ACM-SIAM Symposium on Discrete Algorithms (SODA), 132-139 (2003) (http://research.microsoft.com/~chayes/)に示されている方式を用いると,いわゆるWebのようなつながりを持つグラフを生成する事が出来ます.これは名前の通り,スケールフリー性をもっているため,いろいろな用途に使えそうです.


アルゴリズムは少し複雑です.

アルゴリズムの説明に移る前に,二つの確率密度関数を定義しておきます.

Pin(v = vi) = (din(vi) + δin) / (t + δin・n(t))

Pout(v = vi) = ((dout(vi) + δout) / (t + δout・n(t))

です.

ただしここで,din(vi) と dout(vi) はそれぞれノード vi に入ってくるリンクの数と出て行くリンクの数となり,t は時刻(リンクの合計数でもある),n(t) は時刻 t でのノードの合計となります.

δin と δout はどのようにリンクを張るかのパラメータとなり,定数となります.関数 Pin() は入ってくるリンクが多いほど,リンクを張られる確率が高くなっていますが,δin を大きくすると,その確率の差を小さくする事になります.δout も同じ働きです.

あとは,下記に記されたステップを繰り返すと生成する事が出来ます.

  1. 確率αで新たなノード v を生成し,その v から既存のノード w へとリンクを張る.ただし,w は確率密度関数 Pin() に従ってランダムに選ばれる.
  2. 確率βで既存のノード v と w を選び,v から w へとリンクを張る.ただし,v, w はそれぞれ,確率密度関数 Pout(), Pin() に従ってランダムに選ばれる.
  3. 確率 1 - α - βで新たなノード w を生成し,既存のノード v から w へとリンクを張る.ただし,v は確率密度関数 Pout() に従ってランダムに選ばれる.


Bollobasらの論文によると,Webと同じような特徴を持つグラフを生成したい場合,α = 0.41,β = 0.49,δin = 0,δout = 0,もしくは,α = 0.41,β = 0.59,δin = 0.24,δout = 0 とすればよいらしいです.


Pythonでのプログラムは以下の通りとなります.

#!/usr/bin/env python
# this program generates directed scale-free graphs
#
# see "Directed scale-free graphs", B. Bollobas, C. Borgs and O. Riordan,
# Proceedings of the 14th Annual ACM-SIAM Symposium on Discrete Algorithms
# (SODA), 132-139 (2003)

import random


class node:
    def __init__(self, id):
        self.inLink  = set()
        self.outLink = set()
        self.id = id


class dirsf:
    def _initialize(self):
        random.seed()

        self.nodes = {}
        self.nodes[0] = node(0)
        self.nodes[1] = node(1)
        self.nodes[2] = node(2)

        self.nodes[0].outLink.add(1)
        self.nodes[1].inLink.add(0)
        self.nodes[1].outLink.add(2)
        self.nodes[2].inLink.add(1)
        self.nodes[2].outLink.add(0)
        self.nodes[0].inLink.add(2)

        self.edge = 3
        self.n    = 2


    def generate(self, step, alpha, beta, din, dout):
        self._initialize()

        for var in range(step):
            rnd = random.random()
            print self.n

            if rnd < alpha:
                self.n += 1
                fromNode = node(self.n)
                toNode   = self._getNode(din, self._probIn)

                fromNode.outLink.add(toNode.id)
                toNode.inLink.add(fromNode.id)

                self.nodes[self.n] = fromNode
            elif rnd < alpha + beta:
                while True:
                    fromNode = self._getNode(dout, self._probOut)
                    toNode   = self._getNode(din,  self._probIn)

                    if toNode.id in fromNode.outLink:
                        continue
                    
                    if fromNode.id != toNode.id:
                        fromNode.outLink.add(toNode.id)
                        toNode.inLink.add(fromNode.id)
                        break
            else:
                self.n += 1
                fromNode   = self._getNode(dout, self._probOut)
                toNode = node(self.n)

                fromNode.outLink.add(toNode.id)
                toNode.inLink.add(fromNode.id)

                self.nodes[self.n] = toNode

            self.edge += 1


    def _probIn(self, n, delta):
        din = float(len(self.nodes[n].inLink))
        return (din + delta) / (self.edge + delta * len(self.nodes))


    def _probOut(self, n, delta):
        dout = len(self.nodes[n].outLink)
        return (dout + delta) / (self.edge + delta * len(self.nodes))


    def _getNode(self, delta, prob):
        while True:
            n = int(random.uniform(0, len(self.nodes)))
            rnd = random.random()
            p = prob(n, delta) / len(self.nodes)

            if rnd < p:
                return self.nodes[n]


    def printLink(self):
        for n in range(self.n):
            for node in self.nodes[n].outLink:
                print '(%d, %d)' % (n, node)


    # draw with NodeBox
    # see http://nodebox.net/code/index.php/Graph
    def draw(self, width, height):
        try:
            graph = ximport("graph")
        except ImportError:
            graph = ximport("__init__")
            reload(graph)

        size(width, height)
        g = graph.create()

        for n in range(self.n):
            if len(self.nodes[n].outLink) == 0:
                continue

            for id in self.nodes[n].outLink:
                g.add_edge(n, id)

        g.solve()
        g.styles.apply()
        g.draw(directed = True, traffic = 1)
            

if __name__ == "__main__":
    graph = dirsf()
    graph.generate(1000, 0.41, 0.59, 0.24, 0.0)

    graph.printLink()

"""
graph = dirsf()
graph.generate(1000, 0.41, 0.59, 0.24, 0.0)

graph.draw(1440, 900)
"""


NodeBoxを用いてvisualizeしてみたら,以下のようになりました.