2. 道路ネットワーク上の移動モデルを作成する①¶
本節では、実際の都市空間への適用を想定した、道路ネットワーク上での移動モデルを作成します。artisocでは、交差点部分をノード、道路部分をリンクとして読み込むことで、道路ネットワークを表現します。
2.1. モデルの概要¶
例として、下図のようなシンプルな道路ネットワークを考えます。前節ではノードは人、リンクは人の繋がりでしたが、本節ではノードは交差点、リンクは交差点を繋ぐ道路を意味します。
本節では、テキストファイルで定義された道路ネットワークデータを読み込むことで、モデル上にネットワークを作成します。さらに、そのネットワーク上で人がランダムに動くルールを作成しましょう。
それでは、モデルを作成していきます。まずは、ネットワークを読み込むための下準備をします。
「新規モデルの作成」から新しくモデルの画面を開き、モデルには「道路モデル」と名前を付けましょう。
次にルール画面を表示し、モデルツリーの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(コンマ区切り)」を選択することに注意してください。
作成したファイルをモデルに登録しましょう。編集画面左下の「入力ファイル」画面の「+」ボタンを押して、ファイルをインポートします。
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_jis
かcp932
を文字コードとして指定すると解消することが多いです。
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です。
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 |
エクセルで作成する場合には、以下のようになります。
作成したら、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_nodes
のfrom_node_number
番目を取得しているわけです。このように、リスト型変数から要素を取り出すためにはブラケット[]
を用いて要素番号を指定します。なお、要素番号は1番でなく0番から始まることに注意しましょう。
同様に、to_node = Universe.all_nodes[to_node_number]
でリンクの終点ノード(エージェント)を取得します。
最後に、from_node.link.add(to_node)
で起点ノードの変数linkに終点ノードを追加します。linkは集合型(セット型)の変数ですので、appendではなくaddを使うことに注意してください。
これでリンクを定義できました。この状態で保存して実行してみましょう。線が引かれていれば成功です。
2.4. 参考¶
この章のサンプルモデルは次の通りです。