MATLAB公交线路仿真:从发车表到车辆运行模型
2026/9/13 12:48:24 网站建设 项目流程

简介:面向交通仿真与城市公交优化的MATLAB建模资源,适合具备一定编程基础的交通专业学生、科研人员及调度策略开发者使用。压缩包内仅包含一个M脚本,整包约两KB,却完整实现了公交线路仿真中的车辆行驶与乘客上下车两大模型:车辆模型通过微分方程刻画车速、加速度、道路阻力和交通信号约束;乘客模型基于离散事件仿真,用泊松分布模拟到站客流,以均匀或指数分布描述上下车时间。脚本整合了状态机、事件调度与主循环,可直接修改参数并运行,便于观察不同道路条件和发车方案下的系统表现。已有256人学习下载。借助这份源码,读者能快速搭建仿真原型,计算平均候车时间、车辆占用率等关键指标,并测试调整发车间隔、增减线路等策略,为公交系统评估与改进提供量化支撑。

1. 公交线路仿真:从发车表到车辆运行模型

公交线路仿真的核心不是把线路画出来,而是用一组可调参数描述“车在什么时刻到哪一站、停多久、能上多少人”,然后让这批车在电脑里跑上几个小时,产出一组能指导排班和调度的指标。直接拿 Excel 拉公式也能算平均间隔,但一旦涉及随机波动(乘客到达、站间延误、上下客时间)、车辆互相影响以及满载带来的过站,就必须上仿真。MATLAB 做这件事的便利在于它自带的随机数、统计函数和画图工具,能让你把模型从脚本快速推向实验。

这个 bus_line_model 适合三种场景:运营方案对比(高峰期加车 vs 区间车)、线路参数标定(确定合理发车间隔)、以及算法验证(比如动态调度策略)。它不追求渲染场景,也不需要接入实时数据,只要你和业务方认可用平均值和分布来描述现实即可。下面从模型边界开始讲。

2. 建立公交线路模型的三个核心要素:站点、车辆与乘客到达

2.1 站点与线路拓扑的数据结构

做公交线路仿真,第一步不是写代码,而是决定用数据模型表示一条线。常见做法是定义两个数组:站点列表stops和区段行驶时间tripTime。站点列表用一维坐标或序号表示顺序,区段时间数组表示“从第 k 站开到第 k+1 站”的期望用时。线路类型分为两种:往返型(车到终点后原路返回)和环型(所有站点首尾相接)。在 MATLAB 里,环型实现起来更省事,因为车辆永远不会“下线”,只需用mod(index, N)处理下一站索引。

我一般用struct而不是多个分散变量来保存线路数据,这样往函数里传参方便。下面是一个最小站点表的结构:

字段类型说明
s.iddouble站点编号,1 到 N
s.namestring站名,仅调试用
s.demanddouble单位小时乘客到达数(人/小时)
s.alightProbdouble到达本站的下车概率(占车上人数比例)
tripTime(k)double从站点 k 到下一站的基础行驶时间(秒)

在实际项目中,tripTime不应是固定值,而应从历史 GPS 轨迹里取每个区段的均值和标准差,然后用随机数生成每一次实际通行时长。s.demand也要按高峰/平峰拆成多个时段值,仿真时根据当前时间选用对应参数。这样建出来的模型才具备对真实线路的还原度,而不是一个“会动的图”。

2.2 车辆运行时间与 dwell time 的随机建模

车辆从 A 站驶向 B 站的时间,受红绿灯、拥堵和上下客影响。一般把站间行驶时间建模为正态分布,并做截断:actualTime = max(mu + sigma * randn, 0.5*mu)。不建议用均匀分布,因为现实中极端延误虽然少,但会造成车辆串车(后车紧跟前车),这是公交仿真里最需要捕捉的现象。方差的设定直接决定串车发生频率:方差越大,越容易发生后车追上前车,导致前车更挤、后车更空。

停站时间(dwell time)与上下车人数强相关,最常用的线性模型是:

dwell = c0 + c1 * board + c2 * alight

其中c0是基础开门关窗时间(约 2 秒),c1是每位上车乘客平均耗时(约 0.8 秒),c2是每位下车乘客平均耗时(约 0.6 秒)。实际中乘车人数多时,上下车会并行,因此有人会改用max(board, alight)而不是两者相加。在 MATLAB 里计算时,建议对dwell向上取整到整数秒,同时设置最小值为 5 秒,避免因乘客数少导致停靠时间变成负数。

2.3 乘客到达率与上下客流量生成

乘客到达的经典假设是泊松过程,即单位时间到达人数服从泊松分布:poissrnd(lambda * dt / 3600)。其中lambda是每小时平均到达率,dt是仿真时间步长(秒)。这个公式对 dt 较小时近似成立,但如果dt过大(比如 60 秒),一次累积生成的人数偏多,会掩盖乘客到达的随机波动,因此 dt 不要超过 5 秒。

下车乘客建模有两种方式:一种是用 OD 矩阵(出发站×到达站的比例),另一种是简化为“每站按概率下车”。OD 矩阵精度高,但标定成本高;我通常先用下车概率做预研,后续有刷卡数据再升级。下车人数生成用二项分布:binornd(onboard, alightProb(s))。这里要注意alightProb需要满足整条线路各站概率之和小于等于 1(实际会略小于 1,因为部分乘客坐满全程)。

3. MATLAB 实现最小可运行 bus_line_model:事件驱动还是时间步进

3.1 时间步进法的主循环框架

公交仿真有两种流派:事件驱动和时间步进。事件驱动只在车辆到站、发车等离散时间点更新状态,效率高;时间步进在每个固定步长(如 1 秒)检查所有对象状态,逻辑简单、容易加规则。对于小时级的线路仿真,时间步进法的计算量完全可以接受,所以我推荐先用时间步进跑通,再根据需求决定是否重构。

主循环框架如下:仿真时间从 0 到 T 秒,每一步依次处理“新乘客到达 → 车辆运行状态转移 → 乘客上下车”。下面是 MATLAB 代码的最小实现骨架,注意这个版本环型线路,车辆从站点 1 发车后按 1→2→…→N→1 循环行驶。

% bus_line_sim.m 最小可运行的公交线路仿真(时间步进法) clearvars; clc; rng(2025); % 固定随机种子,保证可复现 % ----- 线路与车辆参数 ----- N = 10; % 站点数 M = 5; % 车辆数 H = 300; % 发车间隔(秒) T = 3 * 3600; % 总仿真时长(秒) dt = 1; % 时间步长(秒) capacity = 80; % 单车容量(人) baseDwell = 2; % 基础停靠时间(秒) dwellPerPerson = 0.8; % 上下一位乘客耗时(秒) % 区段基础行驶时间:tripTime(s) 表示从站点 s 到下一站 tripTimeMu = 80 * ones(1, N); tripTimeSigma = 15 * ones(1, N); lambda = [20 80 120 50 30 10 15 25 60 5]; % 各站乘客到达率(人/小时) alightProb = [0 .1 .2 .1 .05 .1 .15 .1 .05 .15]; % 各站下车概率 % ----- 更新数据结构 ----- waiting = zeros(1, N); % 各站候车人数 events = []; % 记录事件: [时间, 车辆, 站点, 上车数, 下车数, 载客数] % 初始化车辆结构数组 for i = 1:M veh(i).id = i; veh(i).state = 'idle'; % idle: 尚未发车; running: 行驶; dwell: 停靠 veh(i).atStop = 1; % 当前所在站点(或最近经过的站点) veh(i).tRemain = 0; % 运行剩余时间(秒) veh(i).dwellRemain = 0; % 停靠剩余时间(秒) veh(i).onboard = 0; % 载客数 veh(i).departTime = (i-1) * H; % 预定发车时间 end % ----- 主循环 ----- for t = 0:dt:T % 1. 更新乘客到达(泊松过程) waiting = waiting + poissrnd(lambda * dt / 3600); % 2. 更新每辆车状态 for i = 1:M v = veh(i); if strcmp(v.state, 'idle') % 等待发车 if t >= v.departTime % 在起始站完成上下客后发车 [v, waiting, events] = handleStop(v, 1, ... alightProb(1), capacity, waiting, events, t, baseDwell, dwellPerPerson); v.state = 'running'; v.tRemain = tripTimeMu(1) + tripTimeSigma(1) * randn; v.atStop = 1; end elseif strcmp(v.state, 'running') % 行驶中 v.tRemain = v.tRemain - dt; if v.tRemain <= 0 % 到达下一站 nextStop = mod(v.atStop, N) + 1; [v, waiting, events] = handleStop(v, nextStop, ... alightProb(nextStop), capacity, waiting, events, t, baseDwell, dwellPerPerson); v.state = 'dwell'; v.dwellRemain = max(5, ceil(baseDwell + dwellPerPerson * ... (events(end,4) + events(end,5)))); % 按上下车人数延长时间 end elseif strcmp(v.state, 'dwell') % 停靠中 v.dwellRemain = v.dwellRemain - dt; if v.dwellRemain <= 0 v.state = 'running'; v.tRemain = tripTimeMu(v.atStop) + tripTimeSigma(v.atStop) * randn; end end veh(i) = v; end end

代码里的handleStop是一个子函数,负责在车辆到达站点时执行下车、上车、容量限制和事件记录。由于篇幅这里不展开全部行,但其逻辑固定:先下车,再按剩余空间决定上车人数,超出的乘客留在候车区。主循环中的events是 n×6 矩阵,每行保存[时间, 车辆id, 站点, 上车数, 下车数, 载客数],后续所有指标都从它提取。

3.2 车辆位置、站间行驶与停站逻辑

上面的主循环里,车辆状态机只有三个状态,但有一个关键规则必须写对:车辆在running状态减到 0 后,必须先执行handleStop里的上下客,再切换到dwell,而不是先 dwell 后上下客。实际运营中上下客发生在停稳那一刻,随后才计时停靠。我见过不少仿真把顺序写反,导致乘客会在车辆已经离站后才上车,结果指标全乱。

另外,起点站的发车逻辑要单独处理。上例中所有车辆初始状态是idle,到点后在起点站执行一次上下客然后直接出发,相当于把“起始站停靠”压缩到发车瞬间。如果起始站也有候车乘客,这里就自然计入。注意tripTimeMu(N)表示从站点 N 到站点 1 的时间,因此环型线路的行驶时间数组长度必须是 N,而不是 N-1,这是一个容易数组越界的地方。

容量限制的实现按以下顺序:先alight = min(onboard, binornd(onboard, alightProb)),后board = min(waiting, capacity - (onboard - alight))。这里waiting是当前站的候车人数,binornd的用法是每次抽样确定多少人下车。如果车辆满载,board为 0,后续乘客只能等下一班,队列逐渐积累。这个积累过程正是串车现象在指标上显现的原因。

3.3 输出指标:准点率、满载率与平均候车时间

仿真跑完后events矩阵包含了每个站每次车辆到达事件。从它可以计算三个核心运营指标,含义如下表:

指标计算方式用途
车头时距均值对同一站点,按时间排序,计算相邻到达时刻差判断发车稳定性
准点率车头时距落在[0.8H, 1.2H]区间内的比例衡量服务可靠性
平均候车时间若乘客到达均匀,则等于mean(车头时距)/2服务质量核心指标
满载率所有离站事件中载客数/容量的平均值评估运力配置

在 MATLAB 中提取这些指标很简单:

% 假设 events 已在主循环中生成 % 计算每站车头时距 stations = unique(events(:,3)); headwaySum = 0; for s = stations' idx = (events(:,3) == s); times = sort(events(idx,1)); hw = diff(times); hw = hw(hw > 0); % 去掉无意义零间隔 headwaySum = headwaySum + mean(hw); end meanHeadway = headwaySum / length(stations); meanWait = meanHeadway / 2; fprintf('平均候车时间: %.1f 秒\n', meanWait);

注意这里用mean(hw)/2是建立在乘客到达率恒定且车辆按时通过的前提下;如果仿真中高峰时段有变化,更好的做法是直接模拟乘客个体到达时间。但对于初步方案对比,这个近似已经够用。满载率可以直接取events(:,6)capacity求平均。

4. 参数标定与仿真实验设计:发车间隔和客流密度怎么设

4.1 关键参数表与敏感度分析

仿真模型的可信度取决于参数标定。下表列出 bus_line_model 中最关键的输入参数、建议标定来源和常见取值范围:

参数符号含义标定方法常见范围
tripTimeMu区段平均行驶时间从车载 GPS 轨迹提取,按时段求均值30~180 秒
tripTimeSigma区段行驶时间标准差同一数据源求标准差5~30 秒
lambda站点到达率刷卡/OD 数据或人工调查5~150 人/小时
alightProb本站下车概率刷卡数据下车人数/车上人数0.02~0.3
capacity车辆最大载客数车型参数70~120
dwellPerPerson单人上下车耗时视频观察或实测0.5~1.5 秒

敏感度分析的作用是找出哪些参数对指标影响最大。最常见做法是固定其他参数,让H(发车间隔)从 180 秒扫到 600 秒,观察平均候车时间和满载率的变化。MATLAB 用 for 循环批量执行即可:

headwayList = 180:60:600; result = zeros(length(headwayList), 2); for k = 1:length(headwayList) H = headwayList(k); % 重新运行仿真函数 busLineSim(H, ...),返回指标 [meanWait, loadFactor] = busLineSim(H, ...); result(k, :) = [meanWait, loadFactor]; end plot(headwayList, result(:,1), '-o'); xlabel('发车间隔 (秒)'); ylabel('平均候车时间 (秒)');

注意运行仿真函数前要调用rng保持同一随机种子,否则两次仿真的随机波动会掩盖参数变化的影响。对于更正式的敏感度分析,可以用 MATLAB 的set扰动法,即每次只改一个参数为 ±20%,记录指标的变化百分比。

4.2 用 MATLAB 批处理跑多组场景

实际项目中很少只跑一条曲线,更多是同时比较“现状方案”“高峰期加 2 辆车”“缩短发车间隔到 240 秒”“区间车调度”等场景。这时需要把仿真主体封装成独立函数,接收参数结构体并返回指标。封装的好处是场景参数可以用表格组织,批量执行时一行代码搞定。

例如设计一个参数表scenarios

场景名MH区间车区间
base5300
shortH5240
addBus6270
zone5300站点 3~7

批量脚本可以用for遍历表格,将结果写入输出矩阵。这里要注意,如果某个场景新增了“区间车”逻辑,需要在仿真主循环里额外判断车辆是否到达区间终点。一般我建议将调度规则也作为参数传入,这样完全没有必要为每个场景复制一份仿真函数。

批处理的一个常见坑是内存增长。events会随时间增长,跑 100 次仿真可能攒下几十万行,后续分析变慢。解决办法是每次仿真结束后立即计算指标,只保存指标值,不保留原始事件矩阵,除非需要详细回溯。

4.3 结果可视化与对比:时空图与候车时间分布

公交仿真最有说服力的输出不是数字,而是时空图。时空图纵轴为站点编号,横轴为时间,每辆车的轨迹是一条斜线,斜线水平段表示车辆停站。从图上可以直观看到串车:两条斜线几乎平行或相交,说明后车追上了前车。绘制时空图的代码片段:

figure; hold on; for i = 1:M % 从 events 中提取车辆 i 到站时刻和站点 idx = (events(:,2) == i); plot(events(idx,1)/60, events(idx,3), '.-'); end xlabel('时间 (分钟)'); ylabel('站点编号'); grid on;

除了时空图,候车时间分布可以用直方图画出。由于events里没有个体乘客,可以用车头时距的一半作为近似样本,也可以用histogram(events(idx,1))展示到站间隔分布。如果模型支持个体乘客级仿真,则可以绘制真实的候车时间分布直方图,用于对比不同调度方案的公平性,而不仅看平均值。

5. 进阶:加速仿真与验证模型的三个技巧

5.1 用 vectorization 替代 for 循环加速

时间步进法的瓶颈在于每步遍历所有车辆。当车辆数和仿真时长变大时(例如 20 辆车跑 24 小时),dt=1的循环会执行 86400 次,内层再循环车辆,总迭代次数几百万,MATLAB 会比较吃力。一个简单的加速技巧是把“更新乘客到达”向量化,已经在上例中实现。车辆循环目前很难完全向量化,因为车辆之间存在先后顺序导致的依赖,但可以用parfor并行化“不同 H 值的场景”,而不是并行化单次仿真。

另一种做法是把dt适当增大到 2 秒,前提是停站时间和行驶时间都取整到秒。这样总迭代次数减半,误差小于 0.5%。如果你需要极快的实验速度,可以考虑把仿真函数转成 MEX 文件,但收益有限,通常场景并行已经足够。

5.2 与 Simulink 联合仿真的边界

有人会问,为什么不用 Simulink 里的 SimEvents 做离散事件仿真?答案是 SimEvents 的学习成本和配置成本更高,适合复杂的排队网络或资源竞争模型。公交线路如果只是定时发班、单线循环,用脚本仿真更透明。当模型升级到多线路换乘、车辆充电调度、实时控制策略时,SimEvents 的优势才开始体现。我的建议是先用纯 MATLAB 把逻辑验证清楚,再考虑是否迁移到 Simulink。

如果你需要与 Simulink 联合,可以在 Simulink 里调用 MATLAB Function 模块,将核心状态更新函数嵌入。注意仿真时间同步问题:Simulink 的固定步长求解器与我们的dt需要一致,否则会出现状态更新不匹配。同时 MATLAB Function 模块里的代码不支持随机数流rng直接控制,需要在函数外传入随机种子,或者用coder.extrinsic('randn')声明为外部调用。

5.3 用随机种子做可复现性验证

仿真里的随机性来自randnpoissrndbinornd,它们都受全局随机流影响。如果不在仿真前固定种子,每次运行结果都会不同,这会导致实验对比失效。正确做法是给仿真函数增加一个seed输入参数,在函数开头调用rng(seed)。批处理时,每个场景可以固定同一个种子,也可以给不同种子运行多次后取平均,后者用于评估随机波动。

推荐的可复现性验证套路是:对同一组参数,分别运行 1 次和 20 次,比较指标均值的差异。若 20 次平均后的结果与单次相差 5% 以内,说明单次仿真已足够代表系统;若超过 10%,说明随机性过大,需要加长仿真时间或多次取平均。这个验证只花几秒钟,但能避免你拿着一次偶然的仿真结果去调整运营方案。

本文还有配套的精品资源,点击获取

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

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

立即咨询