AI要約
レールごとの長さを反映した上で、より大きなコースでQUBOアニーリングによる衝突回避の最適化に再挑戦しました。コース全体の物理的な距離を正確に扱うことで、より現実的な制御シミュレーションが可能になりました。
やること
前回、QUBOアニーリングで3台の衝突回避の最適化に挑戦しました。
これまでレールの長さはすべて同じにしていましたが、実際には直線レールが21.4cm、カーブレールが16.8cmのようです。今回はレールの長さの違いを反映させつつ、コースを拡張して4台の電車を衝突しないように走らせてみましょう。ここまでできれば実物のデモンストレーションは目の前です。
コースの拡張
前回までのコースを拡張しました。

IDも増えています。

パスと分岐も拡張しました。
#パスの定義
path = {0:1, 1:2, 2:3, 3:4, 4:5, 5:6, 6:7, 7:[8, 14], 8:[9, 20], 9:[22, 10], 10:11, 11:12, 12:13, 13:0,
14:15, 15:16, 16:17, 17:[28, 18], 18:19, 19:6, 20:21, 21:13,
22:23, 23:24, 24:25, 25:26, 26:27, 27:16, 28:29, 29:30, 30:5}
#分岐の状態
switch = {7:0, 8:0, 9:0, 17:0}
#線路の座標、可視化のため
xy_set = {0:[30, 0], 1:[20, 0], 2:[10, 0], 3:[5, 7], 4:[5, 13], 5:[10, 20], 6:[20, 20], 7:[30, 20],
8:[40, 20], 9:[50, 20], 10:[55, 13], 11:[55, 7], 12:[50, 0], 13:[40, 0], 14:[35, 27],
15:[35, 33], 16:[30, 40], 17:[20, 40], 18:[15, 33], 19:[15, 27], 20:[45, 13], 21:[45, 7],
22:[60, 20], 23:[65, 27], 24:[65, 33], 25:[60, 40], 26:[50, 40], 27:[40, 40],
28:[10, 40], 29:[5, 33], 30:[5, 27]}
さて、この後のために追加で2つ定義します。
#パスの長さ、単一長さのレールのみ
length = {0:21.4, 1:21.4, 26:21.4, 27:21.4,
2:16.8, 3:16.8, 4:16.8, 5:16.8, 10:16.8, 11:16.8, 12:16.8, 14:16.8, 15:16.8, 18:16.8,
19:16.8, 20:16.8, 21:16.8, 22:16.8, 23:16.8, 24:16.8, 25:16.8, 28:16.8, 29:16.8, 30:16.8}
#合流レール
join = {6:{5:21.4, 19:16.8}, 13:{12:21.4, 21:16.8}, 16:{27:21.4, 15:16.8}}
「length」はレールの長さ [cm] です。ただし、ターンアウトレールの長さは別で処理するのでここには含めていません。
「join」はターンアウトレールのうち合流となるレールで、どこから来たらどんな長さにするかを書き込みます。
なぜこんなにややこしいことをしているかというと・・・。分岐レールは分岐の状態によって長さが変わります。また、合流レールはどこから来たかによって長さが変わります。これを電車クラスの中でうまく反映させるのに頭を使いました。。
電車クラスの修正
クラス内で「self.len」(現在乗っているレールの長さ)を持つようにします。前回までは進行率が1.0を超えたら次のレールに遷移でしたが、修正後は進行長さがレール長(21.4や16.8)を超えたら遷移とします。よってスピードの意味も「1フレームあたり増やす進行率」から「1フレームあたり進む長さ」に変わっています。
#電車のクラス
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.len = length[pos]
#進行長さ
self.progress = 0.0
#1フレーム進める
def move(self):
#分岐にいる場合、ここでレール長を変更
if self.pos in switch.keys():
s = switch[self.pos]
if s == 0:
self.len = 21.4
else:
self.len = 16.8
#進行長さを加算
self.progress += self.speed
#次のレールに移った場合
if self.progress >= self.len:
self.progress = self.progress - self.len
#分岐からの遷移なら
if self.pos in switch.keys():
s = switch[self.pos]
next_pos = path[self.pos][s]
#そうでないなら
else:
next_pos = path[self.pos]
#合流レールに遷移する場合、ここでレール長を変更
if next_pos in join.keys():
self.len = join[next_pos][self.pos]
#更新
self.pos = next_pos
#まだこのレールにいる場合
else:
pass
move() の開始時に「分岐レールにいる場合は分岐状態によって(この分岐レールの)長さを設定」を差し込み、レール遷移前に「合流レールに行く場合は現在のレールによって(行き先の合流レールの)長さを設定」を差し込んでいます。
合ってるんかいなこれ?
電車を4台に増やして単純シミュレーションしてみましょう。初期位置はターンアウトレールにしないように注意。
#電車の作成
trains = []
trains.append(TrainClass(name='a', speed=0.17*20, pos=0, color='yellow'))
trains.append(TrainClass(name='b', speed=0.23*20, pos=4, color='orange'))
trains.append(TrainClass(name='c', speed=0.41*20, pos=19, color='green'))
trains.append(TrainClass(name='d', speed=0.37*20, pos=22, color='gray'))

レールの長さが反映されているか分からないですが、できていると信じましょう。
アニーリングで衝突回避
では、QUBOアニーリングで500フレームまで進めてみましょう。

最後まで衝突しませんでした!いやこれすごい。永久に行ける感じです。
ちなみに5台に増やしてみると、
trains.append(TrainClass(name='e', speed=0.29*20, pos=26, color='cyan'))

粘っていますが惜しくも238フレーム目で衝突しました。でもかなり頑張ってますよね!?
おわりに
シミュレーションは一旦ここまでにしましょう。実物のデモンストレーションにも挑戦しますので乞うご期待!