☰
Python实现IDM+MOBIL交通流模拟器:从公式到可调试代码
2026/10/3 4:35:33 网站建设 项目流程

简介:本资源是一套基于Python实现的微观交通流仿真系统,面向交通工程、智能网联汽车及计算仿真方向的学习者与研究者,聚焦IDM(智能驾驶模型)在跟驰控制与换道决策中的工程化落地。项目完整复现Treiber-Kesting提出的IDM核心方程,支持车辆加减速动态建模、安全间距判定、相邻车道速度优势评估等关键行为逻辑,并整合可视化模块呈现交通流演化过程。压缩包共16个文件,含8个核心Python源码(如IDM.py、LaneChange.py、Street.py)、2个Jupyter Notebook示例(含可运行仿真演示)、3个备份文件(.zbak)、1个README说明文档及LICENSE协议,总大小546KB,结构清晰、模块解耦,便于理解模型分层设计与代码调用关系。目前已有37人学习下载,读者可直接运行示例、调试参数、拓展多车交互逻辑或对接真实轨迹数据,是深入掌握交通流理论与编程实践结合的优质入门级开源项目。

1. 为什么用 Python 写 IDM 跟驰+换道模型,比调用现成交通仿真软件更值得投入?

你手头有一段城市快速路的浮动车 GPS 数据,采样间隔 2 秒,想验证“前车急刹时,不同反应时间驾驶员的跟驰距离分布是否符合 IDMM(Improved IDM)假设”;或者你在做智能网联车辆协同控制算法的开环测试,需要生成高置信度、带物理约束的合成跟驰轨迹,来替代昂贵的实车编队试验。这时候,打开 VISSIM 或 SUMO 点点鼠标——看似省事,但很快会卡在三个地方:参数不可见、逻辑不可改、数据难导出。VISSIM 的跟驰模型封装在二进制里,你调不了反应时间 τ 的分布函数;SUMO 的 LC2013 换道模型默认只响应车道级拥堵,无法接入 V2X 提供的邻道瞬时加速度预测。而 Python 实现的 IDM+换道模型,不是玩具代码,是能直接嵌入你自己的控制闭环、支持蒙特卡洛参数扰动、输出每辆车每毫秒加速度/位置/换道决策的黑匣子。它适合两类人:一是高校交通工程方向的研究生,需要复现论文模型并做敏感性分析;二是自动驾驶感知-决策-控制链路中的算法工程师,要把交通流作为动态干扰项注入仿真环境。本文不讲 IDM 公式推导(那是教科书干的事),只聚焦一个目标:用不到 300 行可调试 Python 代码,在本地跑通一个带随机扰动、支持多车道自由换道、输出 CSV 轨迹文件的最小可用交通流模拟器。所有代码基于 NumPy + SciPy 构建,零依赖 GUI 库,命令行即可启动,结果可直接喂给 PyTorch 训练轨迹预测模型。


2. 从物理公式到可执行代码:IDM 跟驰模型的 Python 实现与参数校准

IDM(Intelligent Driver Model)的核心价值,在于用 5 个可解释参数刻画人类驾驶行为:期望速度 $v_0$、舒适减速度 $a$、最大加速度 $b$、安全距离系数 $T$、车长补偿 $s_0$。它不是拟合曲线,而是基于“避免碰撞+保持舒适”的双目标优化推导出的微分方程。Python 实现的关键,是把连续微分方程离散化为时间步进迭代,并处理好数值稳定性问题。

2.1 核心公式离散化与 NumPy 向量化实现

IDM 加速度公式为:
$$ a_{\text{IDM}} = a \left[ 1 - \left( \frac{v}{v_0} \right)^4 - \left( \frac{s^(v, \Delta v)}{s} \right)^2 \right] $$
其中安全距离 $ s^
= s_0 + vT + \frac{v \Delta v}{2\sqrt{ab}} $,$\Delta v = v - v_{\text{lead}}$。

注意:不能直接套用公式计算加速度后累加速度得速度——这是新手最常翻车的点。真实场景中,加速度本身是状态变量,需用欧拉法或更稳定的龙格-库塔法更新。我们采用半隐式欧拉法(semi-implicit Euler),即先用上一时刻加速度预估速度,再用新速度重算当前加速度,最后更新位置:

import numpy as np def idm_acceleration(v, v_lead, s, v0=30.0, T=1.5, a=0.73, b=1.67, s0=2.0): """ 计算 IDM 模型瞬时加速度 (m/s²) :param v: 当前车速 (m/s) :param v_lead: 前车车速 (m/s) :param s: 与前车净距离 (m),注意:s = gap - length_lead,非单纯 GPS 距离 :param v0: 期望速度 (m/s),对应 108 km/h :param T: 安全时距 (s),典型值 0.8~2.0,新手建议从 1.5 开始 :param a: 最大加速度 (m/s²),注意不是舒适加速度,是能力上限 :param b: 舒适减速度 (m/s²),决定刹车柔和度,实测值常为 1.5~2.0 :param s0: 静止时最小车头间距 (m),含车身长度补偿 :return: 加速度值 (m/s²) """ delta_v = v - v_lead # 避免除零和负距离导致 NaN s_safe = max(s, s0 * 0.5) # 强制最小有效距离 s_star = s0 + v * T + (v * delta_v) / (2 * np.sqrt(a * b)) # 主公式,注意 v/v0 可能 >1,需 clip 防止负数开方 v_ratio = np.clip(v / v0, 0.0, 1.0) s_ratio = np.clip(s_star / s_safe, 0.0, 10.0) # 防止过大比值导致溢出 acc = a * (1 - v_ratio**4 - s_ratio**2) return np.clip(acc, -b, a) # 物理约束:减速度不超过 b,加速度不超过 a # 时间步进主循环(向量化版,支持多辆车并行) def step_vehicles(vehicles, dt=0.1): """ 批量更新车辆状态:位置、速度、加速度 vehicles: dict of {id: {'x': float, 'v': float, 'acc': float, 'length': float}} 返回更新后的 vehicles 字典 """ ids = list(vehicles.keys()) xs = np.array([vehicles[i]['x'] for i in ids]) vs = np.array([vehicles[i]['v'] for i in ids]) lengths = np.array([vehicles[i]['length'] for i in ids]) # 计算前车距离:对每辆车,找同车道前车(按 x 排序后取前一个) # 此处简化:假设单车道,已按 x 降序排列(车头位置从大到小) # 实际多车道需按 lane_id 分组排序 s_gaps = np.diff(xs) # 相邻车头距离 s_gaps = np.append(s_gaps, np.inf) # 最后一辆车无前车,设无穷大 # 获取前车速度:vs[1:] 对应 vs[:-1] 的前车 v_leads = np.append(vs[1:], 0.0) # 最后一辆车前车速度设为 0 # 向量化计算加速度 accs = idm_acceleration(vs, v_leads, s_gaps, v0=28.0, T=1.6, a=0.8, b=1.8, s0=1.8) # 半隐式更新:先用旧加速度更新速度,再用新速度重算加速度(此处简化为显式) # 更稳定做法:用 accs 更新 v -> 得 v_new -> 用 v_new 和新 s_gap 重算 acc_new v_new = vs + accs * dt v_new = np.clip(v_new, 0.0, 40.0) # 限速 144 km/h # 更新位置:x = x + v_avg * dt,v_avg 取前后速度均值(梯形法更准) x_new = xs + (vs + v_new) / 2 * dt # 写回字典 for i, vid in enumerate(ids): vehicles[vid]['x'] = x_new[i] vehicles[vid]['v'] = v_new[i] vehicles[vid]['acc'] = accs[i] return vehicles

提示:代码中s是净距离(gap),即前车尾部到本车头部的距离。若原始数据只有 GPS 坐标,需减去前车长度(通常取 4.5 m)和本车长度(4.2 m)。很多翻车源于把 GPS 距离直接当s传入,导致s_star/s比值爆炸。

2.2 参数校准:用真实轨迹反推 IDM 参数组合

IDM 参数不是拍脑袋定的。我们用某高速路段 10 辆车的 5 分钟跟驰轨迹(CSV 格式:time,veh_id,x,y,v,acc,lane_id)做校准。核心思路:定义损失函数为预测加速度与真实加速度的 MAE,用 SciPy 的differential_evolution全局优化搜索最优参数。

from scipy.optimize import differential_evolution def loss_function(params, traj_data): """params: [v0, T, a, b, s0]""" v0, T, a, b, s0 = params total_loss = 0.0 # 按车辆分组 for vid in np.unique(traj_data['veh_id']): veh_data = traj_data[traj_data['veh_id'] == vid] # 对每辆车,按时间步计算预测加速度 pred_accs = [] for i in range(1, len(veh_data)): v = veh_data['v'][i-1] v_lead = veh_data['v_lead'][i-1] # 需提前计算前车速度(插值) s = veh_data['gap'][i-1] # 净距离 pred_a = idm_acceleration(v, v_lead, s, v0, T, a, b, s0) pred_accs.append(pred_a) # 与真实加速度比较(跳过首帧) true_accs = veh_data['acc'][1:] total_loss += np.mean(np.abs(np.array(pred_accs) - true_accs)) return total_loss # 执行校准(边界:v0∈[20,35], T∈[0.8,2.5], a∈[0.5,1.2], b∈[1.2,2.5], s0∈[1.0,3.0]) bounds = [(20, 35), (0.8, 2.5), (0.5, 1.2), (1.2, 2.5), (1.0, 3.0)] result = differential_evolution(loss_function, bounds, args=(real_traj,), maxiter=50, popsize=15, seed=42) print("校准后参数:", result.x) # 输出如 [27.3, 1.58, 0.79, 1.72, 1.85]

校准后参数比文献推荐值更“真实”:b=1.72说明该路段驾驶员平均舒适减速度高于理论值 1.67,反映实际驾驶偏激进;T=1.58接近 1.6 秒,符合中国驾驶员跟驰习惯。参数校准不是一次性的,每次更换数据源(如雨天/夜间)都需重跑。


3. 换道决策建模:MOBIL 模型的 Python 实现与车道选择逻辑

IDM 只解决“跟谁”,不解决“在哪条道”。换道行为必须独立建模,否则模拟出的车流全是僵尸——永远卡在最右车道。MOBIL(Minimizing Overall Braking Induced by Lane changes)是目前最被广泛验证的换道模型,其核心思想是:换道必须同时满足两个条件——本车受益(提高自身速度),且不显著伤害周围车辆(不引发连锁急刹)。它比简单的“左车道车速快就换”更符合真实人类决策。

3.1 MOBIL 决策逻辑的 Python 编码实现

MOBIL 判定换道是否发生的公式为:

  1. 意愿条件:$ a_{\text{left}}^{\text{new}} - a_{\text{ego}}^{\text{old}} > a_{\text{th}} $ (换道后本车加速度提升超过阈值)
  2. 安全条件:$ a_{\text{left}}^{\text{new}} + a_{\text{left_lead}}^{\text{new}} > -b_{\text{safe}} $ (换道后本车与左前车加速度和大于安全下限)
  3. 礼貌条件:$ a_{\text{left}}^{\text{new}} + a_{\text{left_lead}}^{\text{new}} > p \cdot (a_{\text{left}}^{\text{old}} + a_{\text{left_lead}}^{\text{old}}) $ (换道带来的总加速度变化,不低于原状态的 p 倍)

其中 $a_{\text{th}}$ 是换道意愿阈值(典型值 0.1~0.3 m/s²),$b_{\text{safe}}$ 是安全减速度(常取 2.0),$p$ 是礼貌因子(0.1~0.5,值越小越自私)。

def mobil_decision(ego, left_lane_vehicles, dt=0.1, ath=0.2, bsafe=2.0, p=0.2): """ 判断 ego 车辆是否应向左换道 :param ego: 字典,包含 'x','v','acc','lane_id','length' :param left_lane_vehicles: 左侧车道所有车辆列表,按 x 降序排列 :param ath: 换道意愿阈值 :param bsafe: 安全减速度阈值 :param p: 礼貌因子 :return: True 表示应换道 """ # Step 1: 找到 ego 在左车道的潜在前车和后车 x_ego = ego['x'] # 在左车道中找 x > x_ego 的最近车(前车) left_leads = [v for v in left_lane_vehicles if v['x'] > x_ego] left_lead = min(left_leads, key=lambda v: v['x']) if left_leads else None # 找 x < x_ego 的最近车(后车) left_follows = [v for v in left_lane_vehicles if v['x'] < x_ego] left_follow = max(left_follows, key=lambda v: v['x']) if left_follows else None # 若无前车,换道收益极大,直接允许(避免堵死) if left_lead is None: return True # Step 2: 计算换道后各车加速度(用当前状态预估) # ego 换道后,前车变为 left_lead,距离 s_ego_new = left_lead['x'] - left_lead['length'] - ego['x'] s_ego_new = left_lead['x'] - left_lead['length'] - ego['x'] a_ego_new = idm_acceleration(ego['v'], left_lead['v'], s_ego_new, v0=28.0, T=1.6, a=0.8, b=1.8, s0=1.8) # left_lead 换道后,后车变为 ego,距离 s_lead_new = left_lead['x'] - left_lead['length'] - ego['x'] # 注意:left_lead 的前车不变,但后车由原后车变为 ego s_lead_new = s_ego_new a_lead_new = idm_acceleration(left_lead['v'], left_lead.get('v_lead', 0.0), s_lead_new, v0=28.0, T=1.6, a=0.8, b=1.8, s0=1.8) # left_follow 换道后,前车变为 ego,距离 s_follow_new = ego['x'] - ego['length'] - left_follow['x'] if left_follow: s_follow_new = ego['x'] - ego['length'] - left_follow['x'] a_follow_new = idm_acceleration(left_follow['v'], ego['v'], s_follow_new, v0=28.0, T=1.6, a=0.8, b=1.8, s0=1.8) else: a_follow_new = 0.0 # Step 3: 检查三个条件 # 意愿条件 if a_ego_new - ego['acc'] < ath: return False # 安全条件:ego 和 left_lead 的加速度和 > -bsafe if a_ego_new + a_lead_new < -bsafe: return False # 礼貌条件:换道后总加速度变化 >= p * 原状态总加速度变化 # 原状态:left_lead 的 acc 不变,left_follow 的 acc 不变(因未换道) # 这里简化:用换道后 left_follow 的 acc 变化代表影响 if left_follow and a_follow_new < -0.5: # left_follow 急刹 # 计算原状态 left_follow 的加速度(假设无 ego 时) s_follow_old = left_follow.get('gap_to_next', 50.0) # 保守估计 a_follow_old = idm_acceleration(left_follow['v'], left_follow.get('v_lead', 0.0), s_follow_old) if a_follow_new - a_follow_old < p * (a_follow_old - (-0.5)): return False return True # 主换道函数:遍历所有车辆,尝试向左/向右换道 def execute_lane_changes(vehicles, lanes, dt=0.1): """ vehicles: 全局车辆字典 {id: {...}} lanes: 车道字典 {lane_id: [vehicle_ids]} """ # 按车道分组车辆(需提前按 lane_id 分组) lane_groups = {} for vid, v in vehicles.items(): lid = v['lane_id'] if lid not in lane_groups: lane_groups[lid] = [] lane_groups[lid].append(v) # 对每辆车,检查是否可向左换道(lane_id+1) for vid, v in vehicles.items(): current_lane = v['lane_id'] left_lane_id = current_lane + 1 if left_lane_id in lane_groups and len(lane_groups[left_lane_id]) > 0: if mobil_decision(v, lane_groups[left_lane_id], dt): # 执行换道:修改 lane_id 和位置(微调 x 避免重叠) vehicles[vid]['lane_id'] = left_lane_id vehicles[vid]['x'] += 0.1 # 微调横向位置,实际应有 y 坐标 return vehicles

注意:MOBIL 的关键在于换道是离散事件,必须在每个时间步单独判定,不能融入连续微分方程。代码中mobil_decision返回True仅表示“应换道”,真正执行lane_id修改在execute_lane_changes中完成,避免边计算边修改导致数据竞争。

3.2 多车道拓扑与边界处理:如何让车辆不“穿墙”

真实道路有硬边界(路肩)、软边界(虚线变道区)、以及入口/出口匝道。模拟中必须显式建模这些:

  • 硬边界:lane_id为 0 或最大值时,禁止向左/向右换道;
  • 软边界:在lane_id=1(最右)和lane_id=2(中间)之间,设置变道概率衰减因子(如prob *= 0.7),模拟驾驶员对实线的敬畏;
  • 匝道处理:在特定x区间(如x ∈ [1000, 1200]),插入虚拟“汇入车”,以v=20±2 m/s匀速从右侧进入主路,初始lane_id=0,然后按 MOBIL 决策并入。
# 示例:在 x=1100m 处添加汇入车 def add_ramp_vehicle(vehicles, next_id, x_ramp=1100.0): new_veh = { 'id': f'ramp_{next_id}', 'x': x_ramp, 'y': -3.5, # 右侧路肩 y 坐标 'v': np.random.normal(20.0, 2.0), 'acc': 0.0, 'lane_id': 0, # 路肩车道 'length': 4.2, 'type': 'ramp' } vehicles[new_veh['id']] = new_veh return vehicles, next_id + 1 # 边界检查函数 def is_lane_change_allowed(vehicles, vid, target_lane): v = vehicles[vid] current_lane = v['lane_id'] # 硬边界:lane_id 0 不能左换,最大 lane_id 不能右换 max_lane = max(lanes.keys()) if lanes else 3 if target_lane < 0 or target_lane > max_lane: return False # 软边界:在实线路段降低概率(此处用 x 位置判断) if current_lane == 0 and target_lane == 1: # 从路肩并入最右道 if 1000 <= v['x'] <= 1200: # 匝道区 return np.random.rand() < 0.9 # 90% 概率并入 return True

4. 避坑指南:IDM+MOBIL 模拟中 5 个血泪经验换来的高频翻车点

模拟器跑起来容易,跑得“像真的一样”极难。以下 5 条是我在复现 TRB 论文、调试深圳湾大桥数据、交付车企仿真需求时,用 CPU 时间和甲方质疑换来的教训。每一条都对应一个具体现象、根本原因和可立即执行的修复方案。

4.1 现象:车辆在低速时频繁“抽搐”(速度在 0~5 m/s 间剧烈跳变)

原因:IDM 公式中 $s^$ 的分母 $\sqrt{ab}$ 在 $v$ 接近 0 时,$v \Delta v$ 项趋近于 0,导致 $s^\approx s_0 + vT$ 过小,而 $s$(净距离)可能因定位误差只有 0.5 m,造成 $s^*/s$ 比值爆炸,加速度突变为极大负值,下一帧又因速度归零而加速度突变为极大正值。
解决:在idm_acceleration函数开头强制约束 $v$ 下限,并对 $s$ 施加物理合理下限:

v = max(v, 0.1) # 速度不低于 0.1 m/s(0.36 km/h) s = max(s, s0 * 0.8) # 净距离不低于 0.8 倍 s0

4.2 现象:所有车辆在 30 秒后集体停止,速度归零,不再移动

原因:时间步长dt设置过大(如dt=1.0)。IDM 是 stiff 微分方程,显式欧拉法在dt>0.2时极易失稳。当dt=1.0,加速度乘以 1 秒,速度一步变化超 10 m/s,导致s瞬间为负,触发idm_acceleration中的clip,加速度被锁死为-b,持续刹车至停止。
解决:严格遵守数值稳定性准则——dt必须小于1/(10*a)。对a=0.8,dt ≤ 0.125。生产环境固定用dt=0.1,若需提速,改用scipy.integrate.solve_ivp的RK45方法。

4.3 现象:换道车辆“鬼穿”其他车,两车x坐标重叠,甚至出现负距离

原因:换道是瞬间完成的(lane_id修改),但位置x未同步调整。车辆从 lane 1 换到 lane 2 时,x不变,导致在新车道上与前车距离远小于安全距离,下一帧 IDM 计算出极大负加速度,但已晚了。
解决:换道时必须微调x位置,使其与新车道前车保持合理初始距离:

# 换道前,计算新车道前车位置 if left_lead: target_x = left_lead['x'] - left_lead['length'] - 20.0 # 设定 20m 初始距离 vehicles[vid]['x'] = max(target_x, vehicles[vid]['x'] - 5.0) # 不向前突进超过 5m

4.4 现象:模拟运行 10 分钟后内存暴涨,Python 进程被 OOM killer 杀死

原因:轨迹数据无节制累积。每辆车每dt=0.1s记录一次x,v,acc,lane_id,10 分钟 = 6000 步,100 辆车 = 60 万条记录,若存为 Python list,内存占用超 2 GB。
解决:用np.memmap创建内存映射文件,或每 1000 步 flush 一次到 CSV:

# 初始化 memmap traj_memmap = np.memmap('traj.dat', dtype='float32', mode='w+', shape=(max_steps, n_vehicles, 4)) # 每步写入:traj_memmap[step, i, :] = [x, v, acc, lane_id]

4.5 现象:MOBIL 换道决策完全失效,所有车永远呆在最右车道

原因:left_lane_vehicles输入未按x降序排列。MOBIL 查找“前车”依赖数组顺序,若输入是乱序列表,min(..., key=lambda v: v['x'])找到的不是空间上前车,而是任意一辆车,导致s_ego_new计算错误。
解决:在mobil_decision开头强制排序:

left_lane_vehicles = sorted(left_lane_vehicles, key=lambda v: v['x'], reverse=True)

5. 从模拟到验证:用三类指标量化你的 IDM+MOBIL 模型可信度

写完代码只是开始,真正的价值在于证明它能回答业务问题。我不会告诉你“模型看起来很酷”,而是给你一套可落地的验证方法论——用真实数据打分,让甲方或导师一眼看懂模型好坏。这里不讲 ROC/AUC 这类通用指标,只聚焦交通流领域公认的三大黄金指标:速度-流量关系(Fundamental Diagram)、换道率分布、跟驰波传播速度。

5.1 Fundamental Diagram:画出你的模型“交通流指纹”

真实高速公路的流量-速度散点图,必然呈现经典倒 U 型:低流量时速度接近 $v_0$,中流量时速度缓慢下降,高流量时速度骤降并出现拥堵波。如果你的模拟结果是一条直线或单调递减曲线,说明 IDM 参数或换道逻辑有硬伤。

操作步骤:

  1. 运行模拟 15 分钟,提取每 30 秒窗口内,所有车辆的平均速度v_mean和总流量q(单位时间通过某断面的车辆数,如q = count / 30);
  2. 用matplotlib绘制q(横轴)vsv_mean(纵轴)散点图;
  3. 叠加真实数据(如 NGSIM 数据集的 US-101 路段图)作对比。
import matplotlib.pyplot as plt # 假设 traj_df 是模拟输出的 DataFrame,含 time, x, v, lane_id 列 traj_df['time_bin'] = (traj_df['time'] // 30).astype(int) # 每30秒一箱 agg = traj_df.groupby('time_bin').agg({ 'v': 'mean', 'id': 'count' }).rename(columns={'v': 'v_mean', 'id': 'count'}) # 流量 q = count / 30 (vehicles per second) agg['q'] = agg['count'] / 30.0 plt.scatter(agg['q'], agg['v_mean'], alpha=0.6, label='Simulated') # 叠加真实数据(此处用 NGSIM 公开数据) ngsim_data = pd.read_csv('ngsim_us101.csv') # 格式:q, v_mean plt.scatter(ngsim_data['q'], ngsim_data['v_mean'], c='red', alpha=0.4, label='NGSIM US-101') plt.xlabel('Flow (veh/s)') plt.ylabel('Mean Speed (m/s)') plt.legend() plt.title('Fundamental Diagram Validation') plt.show()

判据:若模拟点整体位于真实点下方(同流量下速度更低),说明 IDM 的b(舒适减速度)设得过大,驾驶员太“谨慎”;若模拟点向右上方延展(高流量仍高速),说明T(安全时距)太小或s0太小,跟车太激进。

5.2 换道行为验证:统计换道事件的时空分布

真实驾驶员不会均匀换道。他们倾向于在上游拥堵形成前(提前换道避堵)、在下游出口前 500 米(准备驶出)、在车流速度差 > 5 km/h 的车道间(效率驱动)。你的 MOBIL 模型必须复现这些模式。

操作步骤:

  1. 从模拟轨迹中提取所有换道事件:(time, x, from_lane, to_lane, veh_id);
  2. 按x分箱(每 100 米一箱),统计每箱换道次数;
  3. 按time分箱(每 60 秒一箱),统计每箱换道次数;
  4. 计算每次换道前,本车与目标车道前车的速度差|v_ego - v_lead_target|,画直方图。
# 提取换道事件(需在 simulate 循环中记录) lane_change_events = [] # 全局列表 # 在 execute_lane_changes 中,当 vehicles[vid]['lane_id'] 改变时: if old_lane != new_lane: lane_change_events.append({ 'time': current_time, 'x': vehicles[vid]['x'], 'from': old_lane, 'to': new_lane, 'v_ego': vehicles[vid]['v'], 'v_lead': left_lead['v'] if left_lead else 0.0 }) # 绘制空间分布 df_lc = pd.DataFrame(lane_change_events) df_lc['x_bin'] = (df_lc['x'] // 100).astype(int) lc_by_x = df_lc.groupby('x_bin').size() plt.figure(figsize=(12,4)) plt.subplot(1,3,1) plt.bar(lc_by_x.index, lc_by_x.values, width=0.8) plt.xlabel('Position (100m bins)') plt.ylabel('Lane Changes') plt.subplot(1,3,2) df_lc['t_bin'] = (df_lc['time'] // 60).astype(int) lc_by_t = df_lc.groupby('t_bin').size() plt.bar(lc_by_t.index, lc_by_t.values, width=0.8) plt.xlabel('Time (60s bins)') plt.ylabel('Lane Changes') plt.subplot(1,3,3) delta_v = np.abs(df_lc['v_ego'] - df_lc['v_lead']) plt.hist(delta_v, bins=20, alpha=0.7, density=True) plt.xlabel('|v_ego - v_lead| (m/s)') plt.ylabel('Density') plt.show()

判据:若x_bin图在x=1000(模拟入口)和x=5000(模拟出口)出现双峰,且|v_ego - v_lead|直方图峰值在 2~4 m/s(7~14 km/h),说明 MOBIL 的“效率驱动”逻辑生效;若峰值在 0~1 m/s,说明换道过于随意,需调高ath。

5.3 跟驰波传播速度:用“幽灵堵车”检验模型物理真实性

最硬核的验证——制造一次人工扰动,看拥堵波如何传播。在模拟第 300 秒,让第 50 辆车(位置x≈2500m)突然减速至 0,持续 3 秒,然后恢复。记录此后 60 秒内,每辆车开始减速的时刻t_slow[i],计算拥堵波速 $c = \Delta x / \Delta t$。真实世界中,该值约为 15~20 km/h(4~6 m/s)。

操作步骤:

  1. 在模拟循环中加入扰动触发器;
  2. 用np.argmax找到每辆车速度首次低于v0*0.3的时间步;
  3. 对位置相邻的两辆车(如车 i 和 i+1),计算t_slow[i+1] - t_slow[i]和x[i] - x[i+1],得局部波速;
  4. 取所有相邻对的中位数作为最终波速。
# 在 step_vehicles 后加入扰动(示例) if <p> <a href="https://download.csdn.net/download/zru_9602/92714669" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询