Pythonによる交通シミュレーション:追従(IDM) × 車線変更(MOBIL)

Python
この記事は約61分で読めます。

こんにちは、JS2IIUです。

皆さんは、高速道路で自動運転車がスムーズに車線変更をする動画を見たことがありますか? あるいは、普段の運転で「前の車が遅いな、隣の車線に移ろうか」と判断する一瞬、頭の中でどのような計算が行われているのか気になったことはないでしょうか。

今回は、そんな「運転の意思決定」をPythonコードで再現してみよう、というテーマです。

本記事では、ゼロから「交差点の交通流シミュレーション」を作成します。単にランダムに車を動かすのではなく、実際の交通工学や自動運転の研究でも採用されている、物理と心理に基づいた2つの強力な数理モデルを実装します。

  1. IDM (Intelligent Driver Model): 前走車との安全距離を保ちながら加速する「アクセルとブレーキ」のモデル。
  2. MOBIL: 周囲への迷惑と自分の利益を天秤にかけて車線変更を決断する「ハンドリング」のモデル。

これらを組み合わせることで、信号待ちの列、スムーズな発進、そして遅い車を自律的に追い越すダイナミックな交通流を再現します。数式がコードに変わり、目の前で車たちが「思考」し始める瞬間を一緒に体験しましょう。

1. 交通流シミュレーションの基礎理論

コードを書く前に、今回採用する2つのコアアルゴリズムについて、直感的に理解しておきましょう。

アクセルを制御する「IDM」

IDM (Intelligent Driver Model) は、ドライバーが「どれくらいアクセル(またはブレーキ)を踏むか」を決定するモデルです。
ドライバーの心理は、大きく分けて2つの要素で構成されます。

  1. 自由走行への欲求: 前に誰もいなければ、希望速度(例えば60km/h)まで加速したい。
  2. 衝突回避への反応: 前に車がいれば、安全な車間距離を保つために減速したい。

IDMはこの2つのバランスを微分方程式で表したもので、非常に人間らしい滑らかな加減速を再現できるため、多くのシミュレーターで標準的に使われています。

車線変更を決める「MOBIL」

MOBIL (Minimizing Overall Braking Induced by Lane changes) は、その名の通り「車線変更によって引き起こされるブレーキ(減速)を最小化する」モデルです。

車線変更の判断は、以下の2つの基準で行われます。

  • 動機 (Incentive): 車線変更したら、自分はもっと加速できるか?(メリット)
  • 安全性 (Safety): 車線変更したら、変更先のすぐ後ろの車に急ブレーキを踏ませないか?(他者への配慮)

MOBILの面白い点は、「ポライトネス係数 (Politeness Factor)」 というパラメータがあることです。これを調整することで、「周りに迷惑をかけてでも割り込む乱暴な車」や「全体の流れを乱さない紳士的な車」を作り分けることができます。

2. Pythonによる実装設計

それでは実装に入ります。今回は数値計算ライブラリの NumPy と、可視化のために Matplotlib を使用します。

シミュレーションの舞台は、十字路の交差点です。

  • 道路: 東西・南北に走る道路。各方向 片側2車線 とします(追い越しを実現するため)。
  • 信号: シンプルなサイクルで切り替わります。

まずは必要なライブラリのインポートと、モデルの挙動を決める定数(ハイパーパラメータ)を定義しましょう。

Python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
import math

# 時間管理
DT = 0.5  # 1ステップあたりの経過時間(秒)

# --- IDM (追従モデル) パラメータ ---
IDM_PARAMS = {
    'v0': 0.8,    # 希望速度 (Max Speed)
    'T': 1.5,     # 安全車間時間 (Time Headway): 前車とのタイムギャップ
    'a': 0.3,     # 最大加速度
    'b': 0.3,     # 快適減速度
    's0': 2.0,    # 最小車間距離 (停止時の車間)
    'delta': 4.0  # 加速度の急激さを決める指数
}

# --- MOBIL (車線変更モデル) パラメータ ---
MOBIL_PARAMS = {
    'p': 0.2,       # 礼儀正しさ (Politeness factor): 0=自己中, 1=利他
    'b_safe': 0.5,  # 安全ブレーキ限界: これ以上の急ブレーキを後続車に強いるなら変更しない
    'a_thr': 0.1    # 変更閾値: 加速度がこれ以上改善するなら変更する
}

# 道路・車両設定
LANE_WIDTH = 4.0
CAR_LENGTH = 2.0
STOP_LINE = 15.0 # 交差点中心からの距離

3. 実装パート1:追従モデル (IDM) のコーディング

まずは、車の「アクセル操作」にあたる関数を実装します。
この関数は、「現在の自分の速度」「前走車の速度」「前走車との距離」を受け取り、次の瞬間の「加速度」を返します。

Python
def calculate_idm_accel(v, v_leader, gap, params):
    """
    IDMに基づいて加速度を計算する関数
    v: 自分の速度
    v_leader: 前走車の速度 (前走車がいない場合は0や希望速度で扱う工夫が必要)
    gap: 純粋な車間距離 (m)
    params: パラメータ辞書
    """
    v0 = params['v0']
    T = params['T']
    a = params['a']
    b = params['b']
    s0 = params['s0']
    delta = params['delta']

    # 接近率 (相対速度: 正なら近づいている)
    delta_v = v - v_leader

    # IDMの核となる「希望車間距離(s_star)」の計算
    # 必要な距離 = 停止距離(s0) + 車間時間分(v*T) + ブレーキ距離項
    s_star = s0 + v * T + (v * delta_v) / (2 * math.sqrt(a * b))

    # 衝突回避のためのゼロ除算防止
    if gap <= 0.1:
        gap = 0.1

    # 最終的な加速度の計算
    # 第1項: 自由走行での加速 (1 - (v/v0)^delta)
    # 第2項: 前走車との相互作用 (- (s_star/gap)^2)
    acc = a * (1 - (v / v0)**delta - (s_star / gap)**2)
    return acc

この数式の実装により、前が空いていれば a で加速し、近づきすぎると b で減速するという挙動が自然に生まれます。

4. 実装パート2:車線変更モデル (MOBIL) のコーディング

次に、今回のメインディッシュである MOBIL の実装です。
これは Car クラスのメソッドとして実装するのが適切です。なぜなら、自分自身の状態だけでなく、隣の車線の状況(近傍車両)を把握する必要があるからです。

まずは、自分の周囲の車を探すヘルパーメソッド get_neighbors を定義し、それを使って MOBIL の判断を行う check_lane_change を作ります。

Python
class Car:
    # ... (初期化メソッド __init__ は後述の全体コード参照) ...

    def get_neighbors(self, cars, check_lane_offset=0):
        """
        指定した車線(現在の車線 + offset)における
        「前走車(leader)」と「後続車(follower)」を探す
        """
        target_lane_id = self.lane_id + check_lane_offset

        # 車線が存在しない場合(道路外)
        if target_lane_id < 0 or target_lane_id > 1:
            return None, None

        leader = None
        follower = None
        min_dist_leader = 10000.0   # 十分大きな値
        min_dist_follower = 10000.0

        # 全車両をループして、指定車線にいる最も近い前後車両を探す
        # (座標計算の詳細は複雑になるため、ここではロジックの概要のみ解説します)
        # 実際には self.direction ('H' or 'V') と座標を使ってフィルタリングします

        # ... (探索ロジックは完全版コードを参照) ...

        return leader, follower

    def check_lane_change(self, cars, current_acc):
        """ MOBILモデルによる車線変更判断 """
        # 交差点付近や変更直後は変更禁止にするガード処理
        if self.change_cooldown > 0 or self.is_near_intersection():
            return False

        # 隣の車線(変更先)を特定
        target_offset = 1 if self.lane_id == 0 else -1

        # 変更先の前走車(new_leader)と後続車(new_follower)を取得
        new_leader, new_follower = self.get_neighbors(cars, check_lane_offset=target_offset)

        # --- 安全基準 (Safety Criterion) ---
        # 「もし車線変更したら、新しい後続車はどれくらい減速することになるか?」
        acc_new_follower_tilde = 0.0 
        if new_follower:
            # 仮想的な車間距離でIDMを計算
            gap = self.get_gap(self, new_follower)
            acc_new_follower_tilde = calculate_idm_accel(new_follower.speed, self.speed, gap, new_follower.params)

            # 安全限界(-b_safe)より激しいブレーキが必要なら、変更禁止
            if acc_new_follower_tilde < -MOBIL_PARAMS['b_safe']:
                return False

        # --- 動機基準 (Incentive Criterion) ---
        # 1. 変更後の自分の加速度 (acc_self_tilde)
        gap_to_new_leader = self.get_gap(new_leader, self) if new_leader else 10000.0
        v_new_leader = new_leader.speed if new_leader else 0.0
        acc_self_tilde = calculate_idm_accel(self.speed, v_new_leader, gap_to_new_leader, self.params)

        # 2. ポライトネス (他者への影響)
        # 変更前の車線で、元の後続車は加速できるはず(これをメリットとして加算)
        # 変更先の車線で、新しい後続車は減速するかも(これをデメリットとして減算)
        # ここでは簡易的に「新しい後続車への影響」のみを考慮します
        acc_new_follower_curr = 0.0 # 現在の加速度
        # ... (new_followerの現在の加速度計算) ...

        # MOBILの不等式: (自分のメリット) + p * (他人のメリット) > 閾値
        my_advantage = acc_self_tilde - current_acc
        others_disadvantage = acc_new_follower_tilde - acc_new_follower_curr

        if my_advantage + MOBIL_PARAMS['p'] * others_disadvantage > MOBIL_PARAMS['a_thr']:
            return True # 変更承認!

        return False

このロジックが、自動運転における「判断」の中核です。単に空いているから行くのではなく、「全体として効率が良くなり、かつ危険がないか」を計算しています。

5. 全体統合と可視化

これらを TrafficSimulation クラスで統合し、メインループを回します。

完全なソースコード

以下に、動作する完全なコードを掲載します。これを実行すると、MP4動画(環境によってはGIF)が生成されます。
特に、車線変更をした瞬間に車の色がマゼンタに変わる演出を入れているので、いつ判断が下されたかが視覚的に分かります。

Python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
import math

# ==========================================
# 設定パラメータ
# ==========================================
SIMULATION_STEPS = 1000   # フレーム数
DT = 0.5                 # 時間刻み幅

# IDM (追従モデル) パラメータ
IDM_PARAMS = {
    'v0': 0.8,    # 希望速度
    'T': 1.5,     # 車間時間
    'a': 0.3,     # 最大加速度
    'b': 0.3,     # 快適減速度
    's0': 2.0,    # 最小車間距離
    'delta': 4.0  # 指数
}

# MOBIL (車線変更モデル) パラメータ
MOBIL_PARAMS = {
    'p': 0.2,           # 礼儀正しさ: 0=自己中, 1=利他
    'b_safe': 0.5,      # 安全ブレーキ限界
    'a_thr': 0.1        # 変更閾値
}

STOP_LINE = 15.0         # 停止線位置
SPAWN_RATE = 0.15        # 車両発生率
CAR_LENGTH = 2.0         # 車両長
LANE_WIDTH = 4.0         # 車線幅

# 信号機の設定
GREEN_DURATION = 100
YELLOW_DURATION = 65

# ==========================================
# 物理計算関数 (IDM)
# ==========================================

def calculate_idm_accel(v, v_leader, gap, params):
    """ IDMによる加速度計算 """
    v0 = params['v0']
    T = params['T']
    a = params['a']
    b = params['b']
    s0 = params['s0']
    delta = params['delta']

    delta_v = v - v_leader
    s_star = s0 + v * T + (v * delta_v) / (2 * math.sqrt(a * b))

    if gap <= 0.1: gap = 0.1

    acc = a * (1 - (v / v0)**delta - (s_star / gap)**2)
    return acc

# ==========================================
# クラス定義
# ==========================================

class Car:
    def __init__(self, x, y, dx, dy, direction, lane_id, params):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        # 速度に少しバラつきを持たせて追い越しの動機を作る
        self.speed = params['v0'] * random.uniform(0.7, 1.0)
        self.direction = direction
        self.lane_id = lane_id  # 0:内側, 1:外側
        self.params = params
        self.color = 'cyan'     # 通常色
        self.change_cooldown = 0 # 車線変更直後のクールダウン

    def get_neighbors(self, cars, check_lane_offset=0):
        """
        指定した車線(現在の車線 + check_lane_offset)における
        前走車(leader)と後続車(follower)を探す
        """
        target_lane_id = self.lane_id + check_lane_offset
        if target_lane_id < 0 or target_lane_id > 1:
            return None, None

        leader = None
        follower = None
        min_dist_leader = 10000.0
        min_dist_follower = 10000.0

        # 車線幅の判定用座標
        if self.direction == 'H':
            if self.dx > 0: target_y = -2 - (target_lane_id * LANE_WIDTH)
            else:           target_y = 2 + (target_lane_id * LANE_WIDTH)
            target_x = self.x 
        else:
            if self.dy > 0: target_x = 2 + (target_lane_id * LANE_WIDTH)
            else:           target_x = -2 - (target_lane_id * LANE_WIDTH)
            target_y = self.y

        for other in cars:
            if other is self: continue

            # 同じ方向、かつ指定した車線(座標)にいるか判定
            is_same_lane = False
            if self.direction == 'H' and other.direction == 'H':
                if abs(other.y - target_y) < 0.5: is_same_lane = True
            elif self.direction == 'V' and other.direction == 'V':
                if abs(other.x - target_x) < 0.5: is_same_lane = True

            if not is_same_lane: continue

            # 前後の判定
            rel_dist = 0
            if self.direction == 'H':
                rel_dist = (other.x - self.x) * self.dx
            else:
                rel_dist = (other.y - self.y) * self.dy

            if rel_dist > 0: # 前方
                if rel_dist < min_dist_leader:
                    min_dist_leader = rel_dist
                    leader = other
            else: # 後方
                if abs(rel_dist) < min_dist_follower:
                    min_dist_follower = abs(rel_dist)
                    follower = other

        return leader, follower

    def check_lane_change(self, cars, current_acc):
        """ MOBILモデルによる車線変更判断 """
        if self.change_cooldown > 0:
            self.change_cooldown -= 1
            return False

        # 交差点付近(停止線の内側、または手前すぎる場合)は変更禁止
        pos = self.x if self.direction == 'H' else self.y
        if abs(pos) < STOP_LINE + 5.0:
            return False

        target_offset = 1 if self.lane_id == 0 else -1
        new_leader, new_follower = self.get_neighbors(cars, check_lane_offset=target_offset)

        # 1. 安全基準
        acc_new_follower_tilde = 0.0
        if new_follower:
            gap = 0
            if self.direction == 'H': gap = abs(self.x - new_follower.x) - CAR_LENGTH
            else:                     gap = abs(self.y - new_follower.y) - CAR_LENGTH

            if gap < CAR_LENGTH: return False # 衝突するなら不可
            acc_new_follower_tilde = calculate_idm_accel(new_follower.speed, self.speed, gap, new_follower.params)

            if acc_new_follower_tilde < -MOBIL_PARAMS['b_safe']:
                return False

        # 2. 動機基準
        gap_to_new_leader = 10000.0
        v_new_leader = 0.0
        if new_leader:
            if self.direction == 'H': gap_to_new_leader = abs(new_leader.x - self.x) - CAR_LENGTH
            else:                     gap_to_new_leader = abs(new_leader.y - self.y) - CAR_LENGTH
            v_new_leader = new_leader.speed

        acc_self_tilde = calculate_idm_accel(self.speed, v_new_leader, gap_to_new_leader, self.params)

        acc_new_follower = 0.0
        if new_follower:
            gap_old = 10000.0
            v_old_leader = 0.0
            if new_leader: # ここでは簡易的にnew_leaderとの距離を使う(本来はold_leader)
                 if self.direction == 'H': gap_old = abs(new_leader.x - new_follower.x) - CAR_LENGTH
                 else:                     gap_old = abs(new_leader.y - new_follower.y) - CAR_LENGTH
                 v_old_leader = new_leader.speed
            acc_new_follower = calculate_idm_accel(new_follower.speed, v_old_leader, gap_old, new_follower.params)

        my_advantage = acc_self_tilde - current_acc
        others_disadvantage = acc_new_follower_tilde - acc_new_follower

        total_incentive = my_advantage + MOBIL_PARAMS['p'] * others_disadvantage

        if total_incentive > MOBIL_PARAMS['a_thr']:
            return True

        return False

    def change_lane(self):
        """ 実際に座標を変更する """
        offset_dir = 1 if self.lane_id == 0 else -1
        self.lane_id += offset_dir

        if self.direction == 'H':
            if self.dx > 0: self.y -= offset_dir * LANE_WIDTH 
            else:           self.y += offset_dir * LANE_WIDTH
        else:
            if self.dy > 0: self.x += offset_dir * LANE_WIDTH
            else:           self.x -= offset_dir * LANE_WIDTH

        self.change_cooldown = 20
        self.color = 'magenta' # 車線変更のエフェクト

    def update(self, cars, traffic_light_state, dt):
        # 1. 前走車の探索
        leader, _ = self.get_neighbors(cars, check_lane_offset=0)

        min_gap = 10000.0
        leader_speed = 0.0
        if leader:
            if self.direction == 'H': min_gap = abs(leader.x - self.x) - CAR_LENGTH
            else:                     min_gap = abs(leader.y - self.y) - CAR_LENGTH
            leader_speed = leader.speed

        # 2. 信号(停止線)の考慮
        dist_to_stop = 10000.0
        must_stop = False

        # 信号判定ロジック
        if self.direction == 'H' and traffic_light_state in ['V_GREEN', 'V_YELLOW']:
            if self.dx > 0 and self.x < -STOP_LINE: 
                dist_to_stop = -STOP_LINE - self.x
                must_stop = True
            elif self.dx < 0 and self.x > STOP_LINE: 
                dist_to_stop = self.x - STOP_LINE
                must_stop = True

        if self.direction == 'V' and traffic_light_state in ['H_GREEN', 'H_YELLOW']:
            if self.dy > 0 and self.y < -STOP_LINE:
                dist_to_stop = -STOP_LINE - self.y
                must_stop = True
            elif self.dy < 0 and self.y > STOP_LINE:
                dist_to_stop = self.y - STOP_LINE
                must_stop = True

        # 停止線を「速度0の前走車」として扱う
        if must_stop and dist_to_stop < min_gap:
            min_gap = dist_to_stop
            leader_speed = 0.0

        # 3. 加速度計算 & 車線変更判断
        curr_acc = calculate_idm_accel(self.speed, leader_speed, min_gap, self.params)

        if not must_stop:
            if self.check_lane_change(cars, curr_acc):
                self.change_lane()

        # 4. 物理更新
        self.speed += curr_acc * dt
        if self.speed < 0: self.speed = 0

        self.x += self.dx * self.speed * dt
        self.y += self.dy * self.speed * dt

        if self.change_cooldown < 15 and self.color == 'magenta':
            self.color = 'cyan'

# ==========================================
# シミュレーション管理と描画
# ==========================================

class TrafficSimulation:
    def __init__(self):
        self.cars = []
        self.time = 0
        self.light_state = 'H_GREEN'
        self.light_timer = 0

    def step(self):
        self.time += 1
        self.update_traffic_lights()
        self.spawn_cars()
        for car in self.cars:
            car.update(self.cars, self.light_state, DT)
        self.cars = [c for c in self.cars if -60 < c.x < 60 and -60 < c.y < 60]

    def update_traffic_lights(self):
        self.light_timer += 1
        if self.light_state == 'H_GREEN' and self.light_timer > GREEN_DURATION:
            self.light_state = 'H_YELLOW'
            self.light_timer = 0
        elif self.light_state == 'H_YELLOW' and self.light_timer > YELLOW_DURATION:
            self.light_state = 'V_GREEN'
            self.light_timer = 0
        elif self.light_state == 'V_GREEN' and self.light_timer > GREEN_DURATION:
            self.light_state = 'V_YELLOW'
            self.light_timer = 0
        elif self.light_state == 'V_YELLOW' and self.light_timer > YELLOW_DURATION:
            self.light_state = 'H_GREEN'
            self.light_timer = 0

    def spawn_cars(self):
        if random.random() < SPAWN_RATE:
            route = random.choice(['W-E', 'E-W', 'S-N', 'N-S'])
            lane = random.randint(0, 1) # ランダムな車線に出現

            # 出現座標の計算
            if route == 'W-E':
                y_pos = -2 - (lane * LANE_WIDTH)
                spawn_car = Car(-55, y_pos, 1, 0, 'H', lane, IDM_PARAMS)
            elif route == 'E-W':
                y_pos = 2 + (lane * LANE_WIDTH)
                spawn_car = Car(55, y_pos, -1, 0, 'H', lane, IDM_PARAMS)
            elif route == 'S-N':
                x_pos = 2 + (lane * LANE_WIDTH)
                spawn_car = Car(x_pos, -55, 0, 1, 'V', lane, IDM_PARAMS)
            elif route == 'N-S':
                x_pos = -2 - (lane * LANE_WIDTH)
                spawn_car = Car(x_pos, 55, 0, -1, 'V', lane, IDM_PARAMS)

            # 重なり防止チェック
            is_safe = True
            for c in self.cars:
                dist_sq = (c.x - spawn_car.x)**2 + (c.y - spawn_car.y)**2
                if dist_sq < (IDM_PARAMS['s0'] * 3)**2:
                    is_safe = False
                    break
            if is_safe:
                self.cars.append(spawn_car)

sim = TrafficSimulation()
fig, ax = plt.subplots(figsize=(8, 8))

def draw_background():
    ax.set_xlim(-50, 50)
    ax.set_ylim(-50, 50)
    ax.set_facecolor('#333333')

    # 道路描画
    road_w = 16 
    ax.add_patch(plt.Rectangle((-50, -road_w/2), 100, road_w, color='#555555'))
    ax.add_patch(plt.Rectangle((-road_w/2, -50), road_w, 100, color='#555555'))

    # ライン描画
    ax.plot([-50, 50], [0, 0], color='yellow', linestyle='-', linewidth=2)
    ax.plot([0, 0], [-50, 50], color='yellow', linestyle='-', linewidth=2)
    ax.plot([-50, 50], [-4, -4], color='white', linestyle='--', linewidth=1)
    ax.plot([-50, 50], [4, 4], color='white', linestyle='--', linewidth=1)
    ax.plot([-4, -4], [-50, 50], color='white', linestyle='--', linewidth=1)
    ax.plot([4, 4], [-50, 50], color='white', linestyle='--', linewidth=1)

    # 停止線
    for d in [-1, 1]:
        ax.plot([-road_w/2, 0], [d*STOP_LINE, d*STOP_LINE], color='white', linewidth=3)
        ax.plot([0, road_w/2], [d*STOP_LINE, d*STOP_LINE], color='white', linewidth=3)
        ax.plot([d*STOP_LINE, d*STOP_LINE], [-road_w/2, 0], color='white', linewidth=3)
        ax.plot([d*STOP_LINE, d*STOP_LINE], [0, road_w/2], color='white', linewidth=3)

def init(): return []

def animate(i):
    ax.clear()
    draw_background()
    sim.step()

    for c in sim.cars:
        ax.plot(c.x, c.y, 's', color=c.color, markersize=8, markeredgecolor='black')

    light_h = '#00FF00' if 'GREEN' in sim.light_state else ('#FFFF00' if 'YELLOW' in sim.light_state else '#FF0000')
    light_v = '#FF0000' if 'GREEN' in sim.light_state else ('#FF0000' if 'YELLOW' in sim.light_state else '#00FF00' if 'V_GREEN' in sim.light_state else '#FFFF00')

    ax.text(-45, 45, f"EW: ●", color=light_h, fontsize=20, fontweight='bold')
    ax.text(35, 45, f"NS: ●", color=light_v, fontsize=20, fontweight='bold')
    ax.set_title(f"Time: {i}")
    ax.set_xticks([]); ax.set_yticks([])

ani = animation.FuncAnimation(fig, animate, frames=SIMULATION_STEPS, init_func=init, interval=50)

print("動画生成を開始します...")
try:
    ani.save('traffic_sim_mobil.mp4', writer='ffmpeg', fps=20)
    print("保存完了: traffic_sim_mobil.mp4")
except Exception as e:
    print(f"MP4保存失敗(ffmpeg未検出など): {e}")
    try:
        ani.save('traffic_sim_mobil.gif', writer='pillow', fps=20)
        print("保存完了: traffic_sim_mobil.gif")
    except Exception as e2:
        print(f"GIF保存失敗: {e2}")

plt.close()

6. 実行結果と考察

このコードを実行すると、traffic_sim_mobil.mp4 という動画ファイルが生成されます。

動画の中では、以下のような挙動が観察できるはずです。

  1. 信号待ち: 赤信号で停止線に近づくと、減速してスムーズに停止します(停止線を速度0の前走車と見なすロジックが機能しています)。
  2. 追い越し: 速度の遅い車の後ろについた車が、隣の車線を確認します。条件が整うと車体がマゼンタ色に光り、隣の車線へ移動して加速していきます。
  3. 安全確保: 隣の車線に後続車が迫っている場合、どんなに前が遅くても無理な車線変更は行いません。

これは単なるアニメーションではなく、背後でIDMとMOBILという物理・心理モデルが毎フレーム計算された結果です。

まとめ

今回は、Pythonを使って本格的な交通流シミュレーションを作成しました。

  • IDM を使うことで、車間距離に応じた自然な加減速を実現しました。
  • MOBIL を使うことで、周囲の状況を考慮した自律的な車線変更を実現しました。

このモデルは、自動運転車のアルゴリズム検証や、道路設計(渋滞緩和策の検討)など、現実社会で幅広く応用されています。

次のステップとしては、より複雑な道路ネットワークへの拡張や、強化学習 (Reinforcement Learning) を用いた信号機制御の最適化などが考えられます。PyTorchなどの深層学習フレームワークと組み合わせれば、「渋滞を学習して解消するAI信号機」を作ることも夢ではありません。

最後まで読んでいただきありがとうございます。

コメント

タイトルとURLをコピーしました