简介:面向2017年美国大学生数学建模竞赛B题收费广场交通管理问题,这份压缩包收录了基于元胞自动机的MATLAB仿真代码,适合参赛者、交通流建模学习者以及需要快速上手CA模拟的工程师。代码共9个文件,包含8个.m脚本和1个辅助文件,总计约9KB,脚本围绕收费广场场景实现了车辆生成、换道、前进、边界清理、广场构建及主程序调用等完整流程,结构清晰,便于二次修改与参数调试。目前已有221人学习浏览,可帮助读者理解元胞自动机在道路交通流模拟中的落地方法,掌握从元胞状态定义、更新规则到结果分析的一体化实现,也能为优化收费亭数量、收费策略等方案提供可运行的实验基础。整体具有较强的赛题针对性和工程参考价值。
1. 收费广场的拥堵为什么不能当普通路段来建模
第一次用连续流模型做2017年美赛B题收费广场时,我的仿真在第一个收费亭前就崩了:停车排队是离散事件,跟驰方程碰到速度跳变直接发散。换成元胞自动机(CA)后,问题变成“逐格移动”的规则游戏,车辆选哪个亭、排多久、缴费后怎么汇出,全部可数可统计。这个ZIP里的MATLAB代码就是干这个的:create_plaza、move_forward、switch_lanes等脚本串成完整仿真主链,从车辆生成到广场清空都有对应函数。它适合两类人:一是复现美赛B题、需要把细节讲清楚的参赛者;二是做道路交通仿真的工程师,想参考收费广场场景的CA建模与换道逻辑。
2. 元胞自动机布局与状态定义:从create_plaza.m看建模边界
2.1 为什么是二维元胞而不是一维 NaSch
一维NaSch模型只描述单车道跟驰,而收费广场是典型的多入多出结构:上游若干条高速道的车流,在收费岛前要重新选择服务通道,缴费后再汇入下游。不做换道决策,模型最多能复现“一条路堵到底”,无法比较不同收费亭数量对通行效率的影响。因此这套MATLAB代码把整个广场建模成二维矩阵:行代表车道或收费通道,列代表沿前进方向的位置。元胞长度取7.5 m,时间步长取1 s,这两个值组合后,vmax=4对应约108 km/h,vmax=3对应约81 km/h,参数和真实速度的对应关系非常直观。
create_plaza.m不产生车辆,只负责搭网格、初始化亭位和相关计时器。常见写法是返回plaza矩阵、booth_timer矩阵以及收费通道标记:
function [plaza, booth_timer, booth_lanes] = create_plaza(n_lanes, n_booths, len) % plaza(row, col): 通道row在位置col的状态 % 0 = 空元胞 % 1 = 正常行驶的车辆 % 2 = 正在收费亭接受服务的车辆 % booth_timer(row, col): 状态2车辆剩余服务时间(单位:时间步) % booth_lanes(row): 第row行末端是否为收费亭 rows = max(n_lanes, n_booths); % 收费亭可能比高速车道多 plaza = zeros(rows, len); booth_timer = zeros(rows, len); booth_lanes = false(rows, 1); booth_lanes(1:n_booths) = true; % 前n_booths行末端布置收费亭 end代码里的关键设计是把“状态”和“计时”分开。plaza只回答“这格有没有车、在不在服务”,booth_timer回答“还要服务多久”。如果把服务剩余时间也塞进plaza,move_forward每步都要解析复合状态,主循环会变得很臃肿,排错也更难。车道数取max(n_lanes, n_booths)是为了处理收费亭数量大于高速车道数量的情况:额外的行没有左侧入口,只作为收费区的扩展通道,车辆通过换道进入。
参数选择直接影响仿真行为,下面是跑美赛B题时比较常用的一组取值:
| 参数 | 含义 | 参考值 |
|---|---|---|
| n_lanes | 上游高速车道数 | 3~5 |
| n_booths | 收费亭数 / 收费通道数 | 4~8 |
| len | 广场纵向元胞数 | 200~400 |
| p_arrive | 每步每车道产生新车的概率 | 0.1~0.5 |
| v_max | 车辆最大速度(元胞/步) | 3~4 |
| p_slow | 随机减速概率 | 0.1~0.3 |
| service_mean | 单车缴费平均服务时间(步) | 4~10 |
2.2 开放边界与收费服务的状态翻转
边界条件是仿真第一个容易翻车的地方。环形边界适合车辆总数不变的场景,比如环形车道;收费广场需要持续接入上游车流、不断把完成缴费的车移出系统,所以这套代码采用开放边界:new_cars.m从矩阵左边界按概率放行新车,clear_boundary.m从右边界统计驶离车辆。开放边界的代价是系统车辆数不是常数,主循环里必须分别统计流入和流出,否则无法判断模型是否达到平衡态。
收费服务的状态翻转是另一个容易漏掉的点。状态2不是终点:booth_timer减到0时,clear_boundary应将该车辆统计为驶离,同时把对应元胞清0,否则后面的车永远进不了亭。服务计时单独维护,不受move_forward里的跟驰规则影响。没有这一步,仿真会出现“车停在亭位后就消失”的假象,和实际交通中缴费完成才放行的逻辑不符。
3. 同步更新与换道逻辑:move_forward.m和switch_lanes.m的顺序陷阱
3.1 用旧状态算新状态:NaSch同步更新
CA最容易踩到的坑是“边算边改”。同一时间步内,前车移动后的新位置不能成为后车计算距离的依据。move_forward.m里我先冻结旧矩阵,用旧矩阵计算每辆车的可前进距离,最后写入新矩阵。经典NaSch规则四步:加速、随机减速、安全刹车、移动。一个可直接运行的核心片段如下:
% move_forward.m 同步更新核心片段 old = plaza; % 冻结旧状态 plaza = zeros(size(old)); % 新矩阵全部置空 for row = 1:rows for col = len-1:-1:1 if old(row, col) ~= 1 continue; end % 计算前方连续空元胞数 gap = 0; for step = col+1:len if old(row, step) == 0 gap = gap + 1; else break; end end % NaSch 加速与安全刹车合并 v = min(v_max, gap); % 随机减速 if rand < p_slow && v > 0 v = v - 1; end v = max(0, v); % 移动 new_col = col + v; if new_col <= len if new_col == len && booth_lanes(row) % 进入收费亭,转为服务状态 plaza(row, len) = 2; booth_timer(row, len) = service_mean; else plaza(row, new_col) = 1; end end end end代码先用old冻结上一时刻状态,所有判断都基于old,避免前车新位置污染后车判断。gap统计的是前方空元胞数,遇到前车或收费亭元胞就停下,所以v=min(v_max, gap)不会让车穿进障碍物。随机减速用rand < p_slow,每次以p_slow概率减1格,模拟驾驶员反应差异;最后max(0,v)兜底,避免随机减速减成负速度。进入收费亭元胞时把状态置为2,并初始化booth_timer,这样状态1和状态2的转移就在同一处完成。
如果不用双矩阵同步更新,改成在原矩阵上原地移动,仿真结果会有明显偏差:
| 更新方式 | 表现 | 风险 |
|---|---|---|
| 原地先改后算 | 每步循环次数少,速度略快 | 前车新位置被后车当成旧位置,通行量虚高 |
| 双矩阵同步更新 | 每步多复制一次矩阵 | 结果正确,内存开销可接受 |
3.2 switch_lanes:队列差阈值与随机化
收费广场的换道和普通高速路换道不同:目标不是更高速度,而是更短队列。但如果让每辆车都去选最短队列,系统会剧烈振荡,因为队列长度的反馈有延迟:所有车同时切向最短队,最短队瞬间变最长队,下一拍又集体切回来。常见做法是设置队列差阈值delta_q和换道概率p_switch:
% switch_lanes.m 规则示意 for row = 1:rows for col = 1:booth_start-1 if old(row, col) ~= 1 continue; end q_cur = queue_len(row, col); for dr = [-1 1] nr = row + dr; if nr < 1 || nr > rows || old(nr, col) ~= 0 continue; end % 前视一格,防止换道后追尾 if old(nr, col+1) ~= 0 continue; end q_tar = queue_len(nr, col); if q_cur - q_tar > delta_q && rand < p_switch plaza(nr, col) = 1; plaza(row, col) = 0; end end end end这里额外要求旧状态下目标车道同列和前方一列都为空格,否则换道后会立即追尾。delta_q取2~3,p_switch取0.5~0.7比较合适:阈值太小会频繁换道,阈值太大则完全失去选队能力。queue_len可以预先对每列累计从当前位置到收费亭前的车辆数,避免在双重循环里重复扫描。还有一点,switch_lanes要放在move_forward之前,因为换道决策依赖的队列长度是上一时间步的静态信息,不能等车辆移动后再判断。
4. 跑通main.m并量化评估:收费亭数量与等待时间的权衡
4.1 主循环与参数装配
主循环里各函数的顺序直接影响模型行为。我一般按new_cars → switch_lanes → update_service → move_forward → clear_boundary的顺序执行:先放新车,让新车参与选队;再做换道;然后推进收费亭服务计时;服务结束后腾出的亭位在同一时间步就能被move_forward里的后续车辆进入;最后清理边界并统计驶离车辆。
% main.m 主循环骨架 rng(2023); % 固定随机种子,保证实验可复现 n_lanes = 4; % 上游高速车道数 n_booths = 5; % 收费亭数量 len = 300; % 纵向元胞数 sim_time = 3600; % 模拟3600秒 p_arrive = 0.25; % 每个时间步每车道进入概率 p_slow = 0.15; v_max = 4; service_mean = 5; % 平均服务时间(步) delta_q = 2; p_switch = 0.6; [plaza, booth_timer, booth_lanes] = create_plaza(n_lanes, n_booths, len); throughput = 0; delay_list = []; for t = 1:sim_time plaza = new_cars(plaza, p_arrive); plaza = switch_lanes(plaza, delta_q, p_switch); % 收费服务计时 done = booth_timer > 0; booth_timer(done) = booth_timer(done) - 1; finish = done & (booth_timer == 0); plaza(finish) = 0; % 完成服务,清空亭位 throughput = throughput + sum(finish(:)); % 车辆前进 plaza = move_forward(plaza, v_max, p_slow, service_mean, booth_lanes, booth_timer); plaza = clear_boundary(plaza); endnew_cars放在最前面,刚进入的车辆不会错过本时间步的选队机会。update_service放到move_forward前面,缴费完成的车下一跳立即驶离;如果顺序反过来,完成服务的车会在亭位上空等一个时间步,亭多车多时误差会被放大。show_plaza.m可以在每300步调用一次,内部用imagesc绘制plaza矩阵,直观观察排队是否溢出到上游高速段。
4.2 统计指标与亭数量敏感性
统计量建议至少看三个:通过量throughput、平均排队长度、平均延误。每辆车的进入时刻在new_cars里记录,驶离时刻在clear_boundary里计算:
% 在new_cars.m中记录进入时刻 entry_time(row, 1) = t; % 在clear_boundary.m中计算延误 wait = t - entry_time(row, 1); delay_list(end+1) = wait;平均排队长度可以对每个时间步统计各车道排队元胞数再取平均。下面是一组演示数据,展示不同收费亭数量下的效率差异,具体数值随随机种子和参数浮动:
| n_booths | throughput(辆/3600s) | avg_delay(s) | max_queue(元胞) |
|---|---|---|---|
| 4 | 198 | 34.6 | 42 |
| 5 | 222 | 18.2 | 28 |
| 6 | 231 | 11.5 | 17 |
亭从4加到5收益明显,从5加到6增益变小,继续增加边际效益更低。判断临界点的方法是看avg_delay曲线是否进入平台期。瓶颈在某个时刻会从收费亭容量转为上游到达率:n_lanes × p_arrive = 4 × 0.25 = 1辆/步,而5个亭平均服务能力约为5 / service_mean = 1辆/步,两边刚好接近,此时随机波动会直接反映在排队长度上。
5. 参数标定与随机性控制的三个实操技巧
5.1 固定随机种子,批量跑平均
CA交通流每个元胞都可能随机减速,单次仿真结果抖动很大。主循环前用rng(k)固定种子,同一组参数跑20次,结果取平均值和95%置信区间,曲线才平滑。每次run要重置plaza、booth_timer、throughput和delay_list,不要复用上一次运行留下的静态变量。
5.2 p_arrive不是直接到达率
new_cars里的p_arrive是“每车道每时间步生成概率”,不是流量。真实小时流量除以3600秒再除以车道数才是每车道概率。比如1000辆/小时/车道对应p_arrive=1000/3600≈0.28。直接拿流量数值当概率写进模型,车辆生成量会放大3600倍,必然全线堵塞。
5.3 动态开关收费亭:服务时间置Inf
模拟“动态开放收费亭”时,不要直接删除矩阵的行,那会改变车道索引和换道边界。常见做法是维护一个booth_open布尔向量,关停的亭在booth_timer里把剩余服务时间置为Inf,同时让switch_lanes计算q_tar时把该亭队列长度当作无限大,车辆就不会选择它。这样切换策略只需要每N步改一次booth_open和对应参数,车道结构完全不变,模型行为更可控。关亭后还要特别处理已经在该车道排队但尚未缴费的车辆,软分流到相邻开放车道,否则那几辆车会一直卡在队列里,队列统计永久偏高。
本文还有配套的精品资源,点击获取