做交通仿真的人基本都经历过这个阶段路网画好了、交通需求文件配好了、信号配时表也调了好几轮sumo-gui里车辆跑得欢结果仿真一结束面对目录下堆出来的一堆XML文件人反而懵了。打开文件密密麻麻全是尖括号看得脑壳疼最后只能截一张GUI截图放进报告。这个状态我太熟悉了因为我也是从这个坑里爬出来的。真正把交通仿真软件SUMO玩明白恰恰是从仿真结果分析这一步开始的——建模和调参的功夫都需要在输出数据里兑现成可解释、可比较、可决策的结论。这篇文章就以SUMO仿真结果分析为主题围绕“输出文件怎么生成”“每个字段说的是什么”“怎么把XML转成指标和图表”“仿真结论怎么才算可靠”这四件事展开。适合刚跑通第一次仿真、正准备深挖结果数据的读者也适合已经做了一段时间仿真但总觉得分析环节“差点意思”的朋友。顺便提一句安装相关的事SUMO官方安装包在官网直接下载就行Windows版本装完以后sumo和sumo-gui命令会自动进入PATHLinux发行版一般也自带软件包装好以后建议先用安装目录里自带的examples练手别一上来就自己画路网。1. 用 sumocfg 控制输出仿真结果文件从哪来、怎么生成很多人跑完仿真没留下数据问题就出在启动命令里没写输出参数。sumo-gui打开以后你看到的车辆运动其实是内存里的实时演算程序一关这些数据就全部消失了。想在仿真结束后拿到可分析的结果必须在仿真开始前告诉SUMO“我要什么格式的输出、输出到哪个文件”这一节我们先解决这个前置问题。1.1 命令行、cfg文件还是脚本选择适合你的输出配置方式输出参数有三种常见写法。第一种是直接追加在命令行后面例如sumo -c myproject.sumocfg \ --tripinfo-output tripinfo.xml \ --summary-output summary.xml \ --edge-output edge.xml \ --fcd-output fcd.xml \ --vehroute-output vehroutes.xml \ --emission-output emission.xml这种写法的好处是灵活临时想看什么就加什么坏处是不容易复现。我经常做对照实验上午跑方案A下午跑方案B如果每次都要手敲一大串参数很容易漏掉某一项最后比较结果时才发现两个方案的数据口径不一样白跑一趟。第二种是把参数固化到.sumocfg文件里这也是我现在的主力用法。在output节点里写清楚要哪些输出configuration output tripinfo-output valuetripinfo.xml/ summary-output valuesummary.xml/ edge-output valueedge.xml/ fcd-output valuefcd.xml/ vehroute-output valuevehroutes.xml/ /output /configuration这样做的好处是每个实验方案自成一个项目文件夹sumo -c 方案名.sumocfg一跑输出文件的种类、命名、位置全都在配置里写清楚了复查的时候不会两眼一抹黑。第三种是写Python脚本用sumo命令行工具批量调用适合做多轮参数扫描实验比如同时扫描信号周期和车流规模脚本里把随机种子、起止时间、输出文件名全部参数化循环调subprocess运行。这个方法放到后面第四部分讲重复实验时再展开。1.2 各个输出文件的用途对照表SUMO的输出选项很多新手经常面对一堆选项不知道选哪个。我的经验是先按需索取不要一次全开。输出文件写得越细磁盘占用越大分析时处理起来也越慢。下面这张表是我这几年来反复用到的核心输出按分析目标分好了类输出选项文件内容典型分析场景--tripinfo-output每一辆车从出发到抵达的完整记录计算平均行程时间、总延误、排队时间--edge-output按时间窗口聚合的路段统计找拥堵路段、评价路网运行状态--summary-output每秒/每步的全路网汇总指标看全局车辆数、平均速度、总体延误趋势--fcd-output浮动车数据记录每辆车在每个时刻的位置和速度轨迹回放、精细化路线行为分析--vehroute-output车辆实际行驶路径路径选择、绕路行为、OD分配分析--emission-output油耗、CO2、污染物排放等数据绿色交通评价、信号优化节能对比--netstate-dump每个时间步的路网完整状态需要精确到车道的排队长度与占有率分析这里要提一个很多人忽略的参数--begin和--end。如果你仿真的总时长是6000秒但前600秒属于路网加载阶段统计时想跳过那么直接把--begin 600传给edge、emission等聚合输出项就行不用等跑完以后再做数据清洗。我第一次分析时没注意这个点结果预热阶段的数据混在统计结果里平均行程时间被拉高了一大截后面会专门讲这个坑。2. 带着指标需求读文件tripinfo、edge、summary 各自能支撑什么分析有了输出文件下一步就是读文件。XML格式看着吓人但只要理解了不同文件里的字段含义很多分析工作就是“查字段、取数值、做统计”的机械化流程。这一节把最常用的三类文件逐字段过一遍。2.1 tripinfo每次出行的“病历本”tripinfo.xml里一个典型的元素长这样tripinfo idveh_123 depart12.00 departLaneedge42_0 departPos35.20 departSpeed10.37 arrival138.50 arrivalLaneedge90_1 arrivalPos102.10 arrivalSpeed7.20 duration126.50 routeLength2310.40 waitingTime15.30 timeLoss38.15 speedFactor1.05/每个tripinfo元素就是把一辆车的“一生”摊开了写。depart是出发时刻arrival是到达时刻duration是实际行程耗时routeLength是实际行驶的距离。这几个字段能做什么最简单的就是算平均行程速度routeLength / duration这个指标比平均瞬时速度更接近驾驶员实际感受到的通行效率。再看waitingTime和timeLosswaitingTime统计的是车辆由于走走停停、信号灯等原因处于低速状态的总时长timeLoss则是与“按自由流速度跑完同一段路”相比多花的额外时间。这两个字段经常被拿来做信号优化前后的对比——优化的效果往往不直接体现在总时长上而是体现在每辆车少等了多少时间。有一点必须提醒duration会包含车辆在路网里“动画化”起步后以默认加速度缓慢起步的时间所以如果你只对比两个方案的duration差个零点几秒先不要激动先确认两个方案的车辆出发分布、车型参数是否完全一致否则这种微小时差大概率是模型噪声得不出可靠结论。2.2 edge 间隔数据找出拥堵路段的直接工具--edge-output生成的是按时间段聚合的路段数据。在SUMO里edge是最小管理单元一个edge一般代表两个路口之间的一段路也可以进一步细化到车道。开启输出时可以通过--edge-output begin600 period60这类参数控制间隔切分比如每60秒输出一段。典型结构如下interval begin600.00 end660.00 edge idedge42 traveled1890.20 density0.65 avgSpeed41.30 speedLimit60/ /intervaltraveled表示该时间段内所有车辆在这个edge上行驶的总距离density是平均每单位长度上的车辆数avgSpeed是该间隔内车辆的平均通过速度speedLimit是这个edge的限速。要判断一个路段堵不堵avgSpeed和speedLimit一比就知道avgSpeed跌到限速的60%以下基本可以视为出现了拥堵状态如果density也比较高说明是因为车辆积压导致的速度下降而不是个别慢车拉着平均值玩。另外traveled除以时间段长度可以推出这个edge的总流量结合平均速度可以用“流量除以路段容量”的饱和度来评估是否有瓶颈风险。实际做路段缓慢点排查时我不会一个文件一个文件地翻而是直接让程序跑一遍所有edge按时间段把所有avgSpeed低于某个阈值的edge全部列出来输出成一张“哪段路、哪个时间段、速度为多少”的清单。这种批量筛选写个小脚本就行了后面会给出实操代码。2.3 summary从全局视角看路网是否“健康”summary.xml记录的是每个仿真步的全路网状态结构比较扁平step time120.00 loaded105 running82 waiting31 ended23 meanTravelTime56.30 meanSpeed34.60 meanWaitingTime4.10 /loaded是累计已出发车辆数running是当前正在路上的车辆数waiting是当前因拥堵或信号灯而停车等待的车辆数ended是已完成行驶的车辆数meanTravelTime是已经结束行程车辆的平均行程时间meanSpeed是路网内全部正在运行车辆的平均速度。summary文件适合看“趋势”。比如信号灯周期调长以后waiting曲线是不是波动变小了早高峰车流加载阶段running是否稳定爬升、没有突然的尖峰这些宏观趋势判断用summary最快。但注意summary里的meanTravelTime是每时每刻对“已经结束行程的车”重新计算的所以仿真刚开始那一段只有极少数车跑完均值波动会非常大。分析时要记住这一点不要拿着前几十秒的均值对比几百秒后的均值以为路网性能发生了急剧恶化其实只是样本数量不同。3. 从XML到指标与图表解析脚本怎么写才能不返工XML文件可以直接看但数量一多就完全没法人工处理。根本方法其实就那几类解析XML、提取字段、聚合统计、画图。这一节给出一套可以拿来直接改造使用的Python方案核心是顺手、稳定、可复现。3.1 先解析再做统计别把XML当文本抠处理XMLxml.etree.ElementTree是标准库不需要额外安装依赖处理百万级节点的XML文件也够用。核心用法是ET.parse(xxx.xml)加载文件再用root.iter()遍历想要的标签。下面这段代码把tripinfo.xml转成CSV文件顺便算出几个关键指标的均值我一般把它存成tripinfo_analyze.py反复用import xml.etree.ElementTree as ET import csv tree ET.parse(tripinfo.xml) root tree.getroot() rows [] for trip in root.iter(tripinfo): rows.append({ id: trip.get(id), depart: trip.get(depart), arrival: trip.get(arrival), duration: trip.get(duration), routeLength: trip.get(routeLength), waitingTime: trip.get(waitingTime), timeLoss: trip.get(timeLoss), speedFactor: trip.get(speedFactor) }) with open(tripinfo_cleaned.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows) # 简单统计 durations [float(r[duration]) for r in rows if float(r[duration]) 0] time_loss [float(r[timeLoss]) for r in rows if float(r[timeLoss]) 0] if durations: print(f有效车辆数: {len(durations)}) print(f平均行程时间: {sum(durations)/len(durations):.2f} s) print(f平均延误时间: {sum(time_loss)/len(time_loss):.2f} s)这里有个细节我踩过坑tripinfo元素里可能出现depart和arrival都为空或者duration为0的异常记录这些通常是在仿真开始前或结束后生成的不完整数据。统计时建议先做一步过滤比如只保留duration 0的记录再去算平均值。3.2 路段拥堵清单把edge.xml里的慢速路段抽出来解析edge-output的思路类似但要注意嵌套结构外层是interval里面才是edge。我想要找的是“哪个时间段、哪个路段、速度明显低于限速”所以直接两层循环筛选即可import xml.etree.ElementTree as ET tree ET.parse(edge.xml) root tree.getroot() THRESHOLD_RATIO 0.5 # 限速的50%以下视为拥堵 for interval in root.iter(interval): begin float(interval.get(begin)) end float(interval.get(end)) for edge in interval.iter(edge): avg_speed edge.get(avgSpeed) speed_limit edge.get(speedLimit) if avg_speed is None or speed_limit is None: continue avg_speed float(avg_speed) speed_limit float(speed_limit) if speed_limit 0 and avg_speed speed_limit * THRESHOLD_RATIO: print(f时间[{begin:.0f}-{end:.0f}] 路段 {edge.get(id)}: f平均速度 {avg_speed:.1f} km/h, 限速 {speed_limit:.1f} km/h)跑出来的结果会是一张“拥堵清单”我在做路网改造方案时就是靠这张清单找到需要拓宽或者重新配时的路段。有一个更进阶的用法给--edge-output加--aggregated-output相关的输出项或直接用原生的--edge-output加上--period来控制聚合窗口如果你想看的是高峰期前后15分钟的瞬时状态把--period改成900即可窗口越大数据越平滑窗口越小波动越明显。3.3 把summary画成折线图一眼看出全局指标曲线summary.xml里的数据天生适合画时间序列折线图。用matplotlib画图这段代码很固定import xml.etree.ElementTree as ET import matplotlib.pyplot as plt tree ET.parse(summary.xml) root tree.getroot() time_series [] running_series [] waiting_series [] for step in root.iter(step): time_series.append(float(step.get(time))) running_series.append(float(step.get(running))) waiting_series.append(float(step.get(waiting))) fig, ax1 plt.subplots(figsize(10, 5)) ax1.plot(time_series, running_series, labelrunning vehicles, colorblue) ax1.plot(time_series, waiting_series, labelwaiting vehicles, colororange) ax1.set_xlabel(Simulation time (s)) ax1.set_ylabel(Vehicle count) ax1.legend() ax1.grid(True) plt.savefig(network_summary.png, dpi150)这是最简单的全局曲线。实际工作中我经常把多个方案的曲线画在同一张图里对比比如“方案A现状”“方案B信号优化”的running曲线差异看起来没有文字表格直观但一眼能看出哪个方案更早进入平稳态、哪个方案高峰期车辆积压更少。关于“图表给谁看”有一个经验如果你是要在技术评审会上向同事汇报曲线图、散点图都行如果是要给非交通专业的管理者看那就尽量把XML转成Excel表格加上“较现状方案降低了X%”这种结论级描述而不是把技术分析过程全盘倒出来。4. 仿真结论才站得住随机种子、预热期与重复实验的置信度验证我见过太多人包括当年的我跑完一次仿真就拿着一个数字写进报告里平均行程时间32秒比现状方案提升15%然后就没有然后了。这个结论基本站不住脚。交通仿真跟实验科学一样单次结果是无法代表真实分布的你必须面对三个核心问题随机性、预热期、样本量。4.1 随机种子带来的波动比想象中大得多SUMO里的车辆出发时刻、车辆类型分配、路径选择决策等环节都可能引入随机性影响方式通过--seed参数控制。如果你从没有设置过seed每次仿真用的可能是随机种子那两次跑出来的结果细节可能完全不同。我自己做过一次印象很深的小实验同一个路网、同样的需求流量只改种子从1到5平均行程时间最高和最低之间差了将近9%。这个波动幅度足以左右方案优劣的判断——如果优化方案只比现状好5%那在随机波动面前这个“优化”可能根本不存在。所以凡是涉及“对比评价”的分析建议至少跑5次不同种子的仿真把结果整理成“平均值±标准偏差”的形式再下结论。如果方案差异远大于种子间的波动幅度这个结论才是可靠的。实际执行起来可以用脚本批量跑并自动收集结果for seed in 1 2 3 4 5; do sumo -c myproject.sumocfg --seed $seed \ --tripinfo-output tripinfo_seed${seed}.xml \ --summary-output summary_seed${seed}.xml done批量跑完以后再用第三部分的解析脚本逐文件统计把所有种子的均值合成一张汇总表。4.2 预热期不清除平均数据直接失真所有基于车辆加载型仿真demand-driven的模型都存在“预热期”的问题。仿真一开始路网是空的车辆从起点渐渐驶入前几百秒甚至半小时路上车辆数远小于设计通行能力这时的行程时间、平均速度都过于乐观。如果你把预热期里跑出来的“流畅数据”混进统计最终均值会被人为拉低得出比真实运行状态好太多的结论。解决方法的正确姿势不是在结果文件里做“事后筛选”而是在仿真阶段就用参数控制统计窗口。SUMO里像--edge-output、--emission-output、--summary-output这类聚合输出都可以直接指定统计区间sumo -c myproject.sumocfg \ --begin 1200 --end 7200 \ --edge-output edge_busy.xml \ --summary-output summary_busy.xml这里--begin 1200表示从第1200秒开始记录edge统计前面的预热段直接不输出。对于tripinfo这类全程记录的文件可以在分析脚本里过滤掉arrival 1200的车因为那些车在预热期就已经到达了不反映稳定期路网状态。预热期该设多长没有硬性标准建议先跑一次仿真看一眼summary.xml里的running字段——等到“正在运行车辆数”曲线已经进入稳定的上下波动范围而不是持续单调上升的时候就说明可以开始统计了。我一般设定为仿真时长的前20%为预热如果路网规模大、加载慢要适当延长。4.3 不用“单次均值”用“分布和置信区间”表达结论当你做了多个种子的重复实验怎么把结论呈现出来一个直观的办法是计算置信区间。继续用第三部分的tripinfo解析脚本把5个种子的平均行程时间收集齐import statistics avg_durations [35.2, 33.8, 36.1, 34.5, 35.9] # 5个种子每个种子算出的平均行程时间 mean statistics.mean(avg_durations) stdev statistics.stdev(avg_durations) n len(avg_durations) # 95% 置信区间近似计算 ci_low mean - 1.96 * stdev / (n ** 0.5) ci_high mean 1.96 * stdev / (n ** 0.5) print(f平均行程时间: {mean:.2f} s, 95% 置信区间: [{ci_low:.2f}, {ci_high:.2f}])这个结论表达出来就是方案A的平均行程时间为35.1秒95%置信区间为[33.9, 36.3]秒方案B为31.2秒置信区间为[30.1, 32.3]秒。两段区间完全不重叠那方案B的提升是可靠的如果区间有大量重叠基本可以断定差距是随机噪声造成的。最后识别阈值也要有依据。比如前面示例里拥堵阈值设成限速的50%不是拍脑袋拍的——我做城市干道评价时习惯参考《城市道路工程设计规范》里对“畅通、缓行、拥堵”等级的速度区间划分不同等级道路、不同功能定位阈值应该不同。仿真结论要可信分析口径就要提前明确定义并且整个系列实验过程中不随意改动。说实话仿真结果分析是SUMO工作流里最容易被低估的一环。跑仿真谁都会但能把几万个XML节点变成“拥堵出现的时刻与成因”“方案A比方案B可靠快多少”“随机波动之外的规律是什么”这些才是真正考验积累的地方。我的建议是不要急着把脚本写得多花哨先把输出配置固定下来把字段含义吃透把重复实验的框架搭起来之后再逐步细化分析维度。等你习惯用“多轮实验置信区间追因分析”的方式看待每一次仿真那个时候你再回头读输出文件看到的就不再是尖括号而是一套完整、可复用的证据链了。
阅读完成 · 觉得有帮助?