プラレールを量子シミュレーションで自動運転!
- Tomohiro Mochida
- 11 分前
- 読了時間: 4分
はじめに
プラレールの制御を目指して、Pythonで簡易的な走行シミュレータを作成しました。今回は、複数の電車が同じコースを走行する状況を想定し、仮想空間での動作検証・衝突検出・可視化ができる仕組みを実装しています。
問題:複数の電車が走ると、分岐でぶつかるかも!
電車が複数台いて、それぞれ分岐に差し掛かったとき、どの方向に進むかをちゃんと決めないと…→ 同じレールにぶつかって 衝突 が起きる!
なので「分岐の向き(スイッチの状態)をうまく決めて」→ できるだけ衝突が起きないようにしたい!
概要
本記事では、以下の内容について順を追ってご紹介します。
レール構造のグラフ定義
電車クラスによる進行管理
matplotlib を用いた可視化
衝突判定ロジックの実装
4. 参考にしたサイト
こちらのサイトでは様々なレイアウト事例が紹介されており、とても参考になりました。

レイアウトの構築
まず、各レール(ノード)にIDを割り当て、有向グラフとしてルートを定義します。以下のように、分岐を含むループ構造も表現しています。
ここではレール(数字で表現)をノード、電車の進む方向をエッジのように定義しています。
path[0] = 1:レール0の次は1
path[7] = [8, 14]:レール7は分岐あり
switch[7] = 0 → 8に進む
switch[7] = 1 → 14に進む
path = {
0:1, 1:2, 2:3, 3:4, 4:5, 5:6, 6:7,
7:[8, 14], 8:[9, 20], 9:10, 10:11, 11:12, 12:13, 13:0,
14:15, 15:16, 16:17, 17:18, 18:19, 19:6,
20:21, 21:13
}
switch = {7: 0, 8: 0} # 0は直進、1は分岐を意味します
この switch が分岐状態を制御する辞書です。
この値を最適化するのがQUBO(量子ビット最適化)です!
xy_set = {
0:[30, 0], 1:[20, 0], ...,
14:[35, 27], ...
}
これは path の各ノードが画面上のどこに表示されるかを表します。
この座標を使って、あとで電車やレールを matplotlib で描画します。
電車クラス TrainClass の中身
class TrainClass:
def __init__(self, name='a', speed=0.2, pos=0, color='yellow'):
self.name = name
self.speed = speed
self.pos = pos
self.color = color
self.progress = 0.0
このクラスは、1台の電車の動きを表します。
name: 電車の名前(a, b, c...)
speed: 電車の進む速さ(0.0〜1.0の割合でレールを1本進む)
pos: 今いるレールの番号(後で定義されるレールID)
color: 描画時の色
progress: レール上をどれくらい進んだか(0.0〜1.0)
def move(self):
self.progress += self.speed
if self.progress >= 1.0:
self.progress -= 1.0
# 分岐がある場所かチェック
if self.pos in switch.keys():
s = switch[self.pos]
self.pos = path[self.pos][s]
else:
self.pos = path[self.pos]
上のコードは以下の電車の移動を示しています:
今のレールを少し進む(self.progress += self.speed)
1.0を超えたら次のレールへ移動
分岐点なら → switchの設定を見て次の行き先を選ぶ
それ以外 → pathで指定された次のレールへ
最適化のためのQUBO構築
QUBO(Quadratic Unconstrained Binary Optimization)は「0 or 1の選択肢に重み付けして最良を選ぶ」方法です。
① QUBO変数 q[i, j] の意味
q = symbols_list([num_trains, num_patterns], 'q{}_{}')
q[i][j]:i番目の電車がj番目の分岐パターンを選んだかどうか(0か1)
このあとすぐ、全電車の動きを複数パターンの分岐設定で並列にシミュレーションします。
② 電車は必ず1つのパターンだけ選ぶ
for i in range(num_trains):
H += 100 * (sum(q[i, :]) - 1)**2
これは「i番目の電車は、たった1つの分岐パターンしか選んじゃダメ!」という制約。複数の選択肢を選んだらペナルティがつく(目的関数 H が大きくなる)。
③ 衝突を避けるためのコストを追加
for n in range(1, sim_frames):
for j in range(num_patterns):
switch = patterns[j]
for i in range(num_trains):
trains_mat[i][j].move()
ここでやってるのは:
各分岐パターン(patterns[j])について、
全電車の動きを「先回りして」シミュレーション(100フレーム)
↓
その結果、次のように評価します:
pos_mat = np.array([[trains_mat[i][j].pos for j in range(num_patterns)] for i in range(num_trains)])
for r in rails:
q_select = np.where(pos_mat == r, q, 0)
H += (1/n)**2 * (np.sum(q_select) - 0.5)**2
pos_mat[i][j]: i番目の電車がj番目の分岐パターンで到達するレール
np.sum(q_select): 同じレールに複数の電車が来てるか?
これが0.5(≒1台)からズレるほどペナルティ
→ つまり、できるだけ同じレールに電車が集まらないようにしたい!
④ QUBO形式に変換して量子サンプラーで解く!
qubo, offset = Compile(H).get_qubo() solver = sampler.ArminSampler(verbose=0) result = solver.run(qubo, shots=100, T_num=200)
ここで:
Compile(H):目的関数 H をQUBO形式に変換
ArminSampler:QUBOを解くサンプラー(量子アニーリング的なもの)
⑤ 解の復元と分岐の反映
for r in result[:1]:
arr, subs = Auto_array(r[0]).get_ndarray('q{}_{}')
arr[i][j] = 1 のとき、i番目の電車はj番目のパターンを選んだ
この情報から、今の電車の位置に応じて、switch(分岐)を設定
for i in range(num_trains):
p = np.argmax(arr[i])
my_switch = patterns[p]
for key in switch.keys():
if trains[i].pos == key: switch[key] = my_switch[key]
まとめ
このシミュレーションは、複数の電車が分岐を含む線路上を衝突せずに走ることを目的とし、QUBO(量子最適化)を使って、分岐の状態を自動で最適化しています。
🔑 キーアイデア
全分岐パターンを列挙
各電車の進行パターンをシミュレーション
衝突しにくい分岐設定をQUBOで最適化
最適な分岐に基づいて電車を進行&描画
Comments