2. 道路ネットワーク上の移動モデルを作成する①

本節では、実際の都市空間への適用を想定した、道路ネットワーク上での移動モデルを作成します。artisocでは、交差点部分をノード、道路部分をリンクとして読み込むことで、道路ネットワークを表現します。

2.1. モデルの概要

例として、下図のようなシンプルな道路ネットワークを考えます。前節ではノードは人、リンクは人の繋がりでしたが、本節ではノードは交差点、リンクは交差点を繋ぐ道路を意味します。

../../_images/image_tutorial4-1-1.png

本節では、テキストファイルで定義された道路ネットワークデータを読み込むことで、モデル上にネットワークを作成します。さらに、そのネットワーク上で人がランダムに動くルールを作成しましょう。

それでは、モデルを作成していきます。まずは、ネットワークを読み込むための下準備をします。

  • 「新規モデルの作成」から新しくモデルの画面を開き、モデルには「道路モデル」と名前を付けましょう。

  • 次にルール画面を表示し、モデルツリーのUniverseの下に空間を追加します。空間名は「city」、他はデフォルトのままにしておきましょう。

  • 空間cityの下には「node」という名前でエージェントを追加します。

  • エージェントnodeの変数としてnode_numberとlinkを作成します。

  • ユニバース変数「all_nodes」を作成します。

  • マップの出力設定で、空間cityを出力します。マップ要素リストにエージェントnodeを追加します。さらに、エージェント表示設定でエージェント間に線を引く設定を行います。対象の変数としてlinkを選択してください。

2.2. ファイルからネットワークのノードを読み込む

2.2.1. ファイルの作成と登録

まずはネットワークのノードを表すファイルを作成し、それを読み取ってネットワークを作成します。ファイルの読み込みは様々な場面で使えるので、やり方をマスターしておきましょう。

以下のような「node.csv」を用意します。このデータにはノード番号とxy座標を定義しています。ノード0番の座標は(20,40), ノード1番の座標は(40, 40), ... といった具合です。

node_number

x

y

0

20

40

1

40

40

2

20

20

3

35

20

4

10

10

5

20

5

エクセルで作成する場合は、以下のような画面になります。保存時にファイルの種類として「csv(コンマ区切り)」を選択することに注意してください。

../../_images/image_tutorial4-2-2.png ../../_images/image_tutorial4-2-3.png

作成したファイルをモデルに登録しましょう。編集画面左下の「入力ファイル」画面の「+」ボタンを押して、ファイルをインポートします。

../../_images/image_tutorial4-2-4.png

2.2.2. ファイルの読み込み

次に、このcsvファイルをルールエディタから読み込む練習をします。univ_initに以下のようなルールを書き込んでください。

def univ_init(self):
 
    import csv
    with open("node.csv", mode="r") as f:
        reader = csv.DictReader(f)
        for data in reader:
            print(data["x"])

ルールの意味を1行ずつ説明します。

import csvとは、pythonの標準ライブラリであるcsvを取り込むためのコードです。artisoc cloudはpythonをベースとしたソフトなので、pythonモジュールを利用することが出来ます。

with openとは、ファイルにアクセスするための関数です。引数にファイルの名称とアクセスモード(ここでは”r”、つまり読み込み専用)を指定し、asの後ろでコード上におけるファイルの呼び名(ここでは f)を決めています。

reader = csv.DictReader(f)では、ファイルfを読み込み、変数readerとして返す操作を行っています。

for data in reader:以下では、for文を用いることでreaderの中身を変数dataとして1行ずつ取り出しています。さらに、print(data["x"])でx座標のデータを出力することができます。(これについては、このあと詳しく説明します)

実行結果は以下のようになります。たしかに、各ノードのx座標が0番ノードから順番に出力されていることがわかります。

20
40
20
35
10
20

※ファイルを読み込む際、まれにコンソールにUnicodeDecodeErrorなどのエラーが出る場合があります。これは、ファイルの文字コード変換に失敗した際に出るエラーです。日本語環境で作成されたcsvファイルの場合、with openでファイルを読み込む際に以下のようにshift_jiscp932を文字コードとして指定すると解消することが多いです。

  • with open("node.csv", mode="r", encoding="shift_jis") as f:

  • with open("node.csv", mode="r", encoding="cp932") as f:

2.2.3. 辞書型とDictReader

ここで、変数dataの型について説明しておきましょう。dataは辞書型(ディクショナリ型)の変数です。辞書型の変数は、キーバリュー(値)の組み合わせで構成されています。上の例で言えば、for文の最初の繰り返しにおいて、変数dataは以下のような形になっています。

data = {"node_number": 0, "x": 20, "y": 40}

"node_number", "x", "y"がキー、0, 20, 40がそれぞれのキーに対する値です。「node.csv」で定義した、0番ノードの情報を持っていることが分かります。

辞書型の変数から値を取り出すためには、ブラケット[]でキーを指定します。したがって、以下のよう書くことでキー"x"の値(20)を取り出すことができます。

print(data["x"])  # 「20」がプリントされる

csv.DictReader()は、csvファイルの2行目以降の各行について、1行目の各列をキーとした辞書型の変数を返す関数です(より正確に言えば、2行目以降の各行を表す辞書型変数のリストを返します)。1行目にカラム名を持つ表形式のcsvデータを読み込むときに便利ですので、覚えておきましょう。

2.2.4. ノードの生成

ファイルを読み込む方法を理解したので、改めて、読み込んだデータを基にノードの生成を行います。先ほどのルールを修正して、以下のようなルールを書いてください。

def univ_init(self):
    
    import csv
    with open("node.csv", mode="r") as f:
        reader = csv.DictReader(f)
        for data in reader:
            node = create_agt(Universe.city.node)
            node.node_number = int(data["node_number"])
            node.x = float(data["x"])
            node.y = float(data["y"])

for文の中身が変わったので、その説明をしていきます。

node = create_agt(Universe.city.node)で、新たなnodeエージェントを生成します。

node.node_number = int(data["node_number"])で、生成したノードにノード番号を割り振ります。読み込んだノード番号は、そのままでは文字列型として認識されてしまいます。intは、データを整数に変換するための関数です。

node.x = float(data["x"])で、ノードのx座標を指定します。y座標も同様です。floatによってデータを実数値に変換します。

ここまで書いたら、保存して実行してみましょう。以下の図のように、各ノードが表示されればOKです。

../../_images/image_tutorial4-2-7.png

2.3. ファイルからネットワークのリンクを読み込む

次に、リンクデータとして以下のような「link.csv」を読み込みます。ファイルにはリンクの始点ノード番号と終点ノード番号が含まれています。たとえば、0番ノードは2番ノードと繋がっている、という具合です。

node_number

to_node

0

2

1

3

2

0

2

3

2

4

3

1

3

2

4

2

4

5

5

4

エクセルで作成する場合には、以下のようになります。

../../_images/image_tutorial4-2-6.png

作成したら、node.csvと同じように入力ファイルに追加しておきましょう。

エージェントのリンク関係の定義には、変数linkを用います。自分に繋がっているエージェントはここに追加されることになります。agt_initで、linkを空のset型変数として初期化しましょう。

def agt_init(self):
    self.link = set()  # リンクを空のset型変数として初期化

各エージェントのlinkにノードを登録するための準備として、すべてのノードをユニバース変数のall_nodesにリスト型の変数として格納します。リスト型とは複数のデータを順番つきで格納できるデータ型で、番号(インデックス)を指定すれば個々の要素を取り出すことが出来ます。つまり、「all_nodesのto_node番目のノードをfrom_node番目のノードの変数linkに加える」というルールを書けばよいのです。

まずはall_nodesを作成しましょう。先ほど作成したuniv_initに、以下のように2か所追記してください。

def univ_init(self):

    import csv
    
    # 追記。ユニバース変数all_nodesをリスト型として初期化
    Universe.all_nodes = list()  

    with open("node.csv", mode="r") as f:
        reader = csv.DictReader(f)
        for data in reader:
            node = create_agt(Universe.city.node)
            node.node_number = int(data["node_number"])
            node.x = float(data["x"])
            node.y = float(data["y"])
            Universe.all_nodes.append(node)  # 追記。作成したノードをall_nodesに追加

Universe.all_nodes = list()で、変数all_nodesを空のリストとして初期化しています。

さらに、ノードを作成した後にUniverse.all_nodes.append(node)とすることで、作成したノードをall_nodesに追加します。リスト型変数.append(要素)でリストの末尾に要素を追加することができます。ノードは0番から順番に作成されるので、all_nodesにもノードが0番から順番に格納されることになります。

このall_nodesを用いつつ、ファイルを読み込んでリンク関係を定義します。univ_initに以下のように追記してください。

def univ_init(self):
    
    # ノードの読み込みと作成(略)

    # ここから追記(リンクの作成)
    with open("link.csv", mode="r") as f:
        reader = csv.DictReader(f)
        for data in reader:
            from_node_number = int(data["from_node"])
            to_node_number = int(data["to_node"])
            from_node = Universe.all_nodes[from_node_number]
            to_node = Universe.all_nodes[to_node_number]
            from_node.link.add(to_node)

link.csvをDictReaderで読み込み、for文を回すところまではさきほどと同じ流れです。以下、for文の中身について説明していきます。

from_node_number = int(data["from_node"])で、リンクの起点ノード番号を取得します。

同様に、to_node_number = int(data["to_node"])で、リンクの終点のノード番号を取得します。

from_node = Universe.all_nodes[from_node_number]で、リンクの起点ノード(エージェント)を取得します。リスト型変数all_nodesfrom_node_number番目を取得しているわけです。このように、リスト型変数から要素を取り出すためにはブラケット[]を用いて要素番号を指定します。なお、要素番号は1番でなく0番から始まることに注意しましょう。

同様に、to_node = Universe.all_nodes[to_node_number]でリンクの終点ノード(エージェント)を取得します。

最後に、from_node.link.add(to_node)で起点ノードの変数linkに終点ノードを追加します。linkは集合型(セット型)の変数ですので、appendではなくaddを使うことに注意してください。

これでリンクを定義できました。この状態で保存して実行してみましょう。線が引かれていれば成功です。

../../_images/image_tutorial4-2-1.png

2.4. 参考

この章のサンプルモデルは次の通りです。

チュートリアル4-2(道路ネットワーク①