top of page

プラレールを量子シミュレーションで自動運転!

  • 執筆者の写真: Tomohiro Mochida
    Tomohiro Mochida
  • 11 分前
  • 読了時間: 4分
  1. はじめに

プラレールの制御を目指して、Pythonで簡易的な走行シミュレータを作成しました。今回は、複数の電車が同じコースを走行する状況を想定し、仮想空間での動作検証・衝突検出・可視化ができる仕組みを実装しています。


  1. 問題:複数の電車が走ると、分岐でぶつかるかも!

電車が複数台いて、それぞれ分岐に差し掛かったとき、どの方向に進むかをちゃんと決めないと…→ 同じレールにぶつかって 衝突 が起きる!

なので「分岐の向き(スイッチの状態)をうまく決めて」→ できるだけ衝突が起きないようにしたい!


  1. 概要

本記事では、以下の内容について順を追ってご紹介します。

  • レール構造のグラフ定義

  • 電車クラスによる進行管理

  • matplotlib を用いた可視化

  • 衝突判定ロジックの実装


4. 参考にしたサイト

こちらのサイトでは様々なレイアウト事例が紹介されており、とても参考になりました。


  1. レイアウトの構築

まず、各レール(ノード)に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 で描画します。



  1. 電車クラス 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]

  1. まとめ



このシミュレーションは、複数の電車が分岐を含む線路上を衝突せずに走ることを目的とし、QUBO(量子最適化)を使って、分岐の状態を自動で最適化しています。


🔑 キーアイデア

  • 全分岐パターンを列挙

  • 各電車の進行パターンをシミュレーション

  • 衝突しにくい分岐設定をQUBOで最適化

  • 最適な分岐に基づいて電車を進行&描画

 
 
 

Comments


bottom of page