城市道路交通信号实时控制:从排队模型到Python实现 简介这是2008年全国研究生数学建模竞赛的获奖论文聚焦城市道路交通信号实时控制问题适合备战数学建模比赛特别是“华为杯”的师生学习。论文面向单个交叉路口、线状区域和网络区域三类场景构建了以总延误时间最小为目标的实时配时模型并给出动态调整信号灯周期与绿信比的算法针对泊松分布车流设计了实时交通流序列生成方案通过与韦伯斯特算法及固定配时方案对比验证了实时算法的有效性。资源包含1个PDF文件整体约782KB内容涵盖摘要、问题重述、模型建立与求解、实验对比及对交通管理部门的应用建议结构完整便于研读。已有93人学习下载适合需要参考完整赛题论文结构、模型推导与算法实现思路的参赛者。1. 城市道路交通信号实时控制竞赛论文里的建模思路为什么到今天还能用2008年全国研究生数学建模竞赛的题目里有一道“城市道路交通信号实时控制问题”当时很多人拿到题先想上复杂模型真正拿奖的几篇反而都在做同一件事把路口排队看成随时间变化的量用信号灯调节放水速率。我得承认第一次读到这个思路时觉得太简单可后来在模拟项目X里复现了一版才发现这个“简单”恰好是工程上最稳的起点。这篇文章会沿着这条线讲清楚排队、延误、绿信比之间到底怎么算怎么用Python写一个最小实时控制算法以及从竞赛仿真走到真实路口时会踩到哪些坑。适合正在做课程设计、论文仿真或者想评估自适应信号控制值不值得上的工程人员。2. 先看模型排队、延误与绿信比的数学关系2.1 把路口看成一个动态输入输出系统我第一次把信号控制问题交给新手梳理时听到最多的答案是“能不能直接上强化学习”。但竞赛题给的控制对象根本不需要那么重。把单个交叉口拆开本质是一个多服务台排队系统车辆到达形成输入绿灯放行形成输出排队长度就是两者之差。用累积到达曲线A(t)和累积离开曲线D(t)表示任意时刻t的排队车辆数就是N(t)A(t)-D(t)。D(t)不是想放多少就放多少绿灯期间它受饱和流率S限制红灯期间D(t)基本保持水平。这个水池模型虽然简单却抓住了实时控制最核心的控制量每个相位获得的有效绿灯时间g_e。g_e越大该方向累计离开曲线越陡排队下降越快但路口的总周期是有限的给了南北方向就少了东西方向。实时控制要做的事是根据当前各方向排队N(t)的测量值在下一周期重新分配g_e。这一句话就是“实时”和“定时”的全部区别。要提一个容易忽略的细节到达率q和饱和流率S必须换算到同一时间单位。竞赛题目里通常给的是小时流量而仿真步长是秒我见过不少人在这一步把单位搞错导致排队一直不散。后面的代码里我会先把每小时流量除以3600再进入步进循环。还有一个工程习惯到达率不要用瞬时值最好取过去5分钟的平均否则检测器一个抖动就会让算法误判成过饱和。2.2 目标函数最小延误与最短排队不是一回事很多方案把“排队长度最短”直接当成优化目标这样做出来的配时在仿真里很漂亮现场却经常出现绿灯空放。原因在于排队长度是状态量延误才是用户真正感受到的损失。延误在数学上等于排队长度对时间的积分D∫N(t)dt也就是A(t)与D(t)曲线之间围出的面积。两条曲线分开的面积越大每个驾驶员平均等待越久。举一个极端例子南北方向排队40辆东西方向排队10辆。如果按排队比例分配绿灯南北会拿到接近八成的绿灯时间。但假如东西方向排队车辆已经等了很久南北方向是新排过来的那么按延误积分计算东西方向反而需要优先放行。这就是“最小化排队长度”和“最小化延误”在数学上的差异。竞赛优秀论文里比较稳的写法是把延误作为主目标再用排队长度做约束或代理工程实现时则用排队长度代替延误因为检测器不容易直接测延误。如果再进一步可以把延误按车内人数加权比如公交相位权重放大到普通车辆的2.5倍这就有了公交优先的雏形。竞赛论文里很多队伍在这一点上做文章但我建议先把不加权版本跑通再加权重不然你分不清改善来自算法还是来自权重。停车次数也可以作为目标但它对配时变化不敏感优化时经常推不出稳定梯度。我的习惯是仿真评价用延误在线控制用排队长度验收看通过量和满载率不要只盯一个数字。2.3 约束条件周期时长、相位结构与最小绿灯实时控制不是随意改绿灯必须留在信号控制的基本框架里。首先是周期时长C也就是一组相位完整轮转所需时间。C太长红灯方向延误变大C太短黄灯和清空时间占比变大通行能力反而下降。经典Webster公式给了一个很好用的初始周期C0(1.5L5)/(1-Y)。其中L是一个周期的总损失时间Y是各相位关键流量比之和也就是每个相位到达率与饱和流率的比值中的关键值求和。举个例子某个二相位路口只有南北直行和东西直行南北到达率900veh/h饱和流率1800veh/h流量比0.5东西到达率600veh/h饱和流率1800veh/h流量比0.33则Y0.83。每相位损失时间3秒L6秒。C0(1.5*65)/(1-0.83)14/0.17约82秒。这说明实时调整的周期应当围绕82秒浮动而不是每两秒变一次周期否则损失时间会吃掉通行能力。周期之外还有几个硬约束最小绿灯时间必须满足行人过街和安全清空最大绿灯时间防止某个方向无限等下去黄灯和全红清空时间一般固定不参与优化。实时算法可以把这些约束全写进优化器也可以像我后面的最小实现那样先按排队比例算绿信比再强制限制到最小和最大绿灯区间内。实际路口多数时候不需要秒级最优稳定不犯规比极致最优更重要。相位结构也要先说死先放谁后放谁、左转是否单独放这些都是控制算法的“边界”不是让算法自己发明的东西。3. 用Python复现一个最小实时信号控制算法3.1 先定义输入到达率、饱和流率与相位结构在实际项目中输入来自线圈检测器、视频检测器或卡口数据。这里先做二相位路口的简化演示南北方向和东西方向轮流获得绿灯黄灯清空时间固定。代码开头把所有量定义清楚# city_signal.py # 一个简化二相位路口NS 表示南北方向EW 表示东西方向 # 到达率单位 veh/h由检测器统计最近5分钟平均流量换算而来 arrival_rate {NS: 900, EW: 600} # 饱和流率单位 veh/h绿灯完全启亮时车道能通过的最大流量 # 一般城市直行车道取 1800 左右混行或上下坡需要实测修正 sat_flow {NS: 1800, EW: 1800} # 损失时间黄灯 全红清空单位秒 # 这个值安全相关实时控制里不压缩 lost_time {NS: 3, EW: 3} # 初始周期单位秒可以由 Webster 公式估算后人工设定 cycle 60这段代码只定义数据不执行任何控制逻辑。arrival_rate是每小时的车辆数但仿真步进要按秒算所以后面函数里我会先做一次单位换算。sat_flow是最容易被误用的参数很多人把理想条件下的1800直接套到所有路口遇到机非混行、车道变窄、公交车停靠时实际通行能力会掉到1500甚至更低。lost_time包括黄灯和全红清空时间它的作用是“占用周期但不产生有效通行”在计算绿信比时要先扣除。3.2 用时间步进计算排队长度和延误有了输入数据下一步是模拟一个周期内排队和延误的变化。核心思路是逐秒推进每一秒先让所有方向都有新到达再只让当前绿灯方向放行放行量受排队车辆数和饱和流率双重限制def simulate_one_cycle(cycle, greens, rates, sat_rates, initial_queue, loss_times, phase_order, step1.0): # rates 和 sat_rates 先换算成 veh/s rates {ph: rates[ph] / 3600.0 for ph in rates} sat_rates {ph: sat_rates[ph] / 3600.0 for ph in sat_rates} queue dict(initial_queue) delay_area 0.0 for phase in phase_order: # 先放当前相位的有效绿灯 for _ in range(int(greens[phase] / step)): for ph in queue: queue[ph] rates[ph] * step if ph phase: # 放行量不能超过排队车辆数也不能超过饱和流率 discharge min(queue[ph], sat_rates[ph] * step) queue[ph] - discharge delay_area sum(queue.values()) * step # 损失时间车辆仍然到达但不允许放行 for _ in range(int(loss_times[phase] / step)): for ph in queue: queue[ph] rates[ph] * step delay_area sum(queue.values()) * step return queue, delay_area这段代码模拟的是保守策略损失时间内不许放行但到达还在累计。这样排队和延误估计偏保守在实时控制里偏保守通常比偏乐观安全。min(queue[ph], sat_rates[ph]*step)这行是关键它区分了两种状态当排队车辆小于一秒能放行的车辆数时绿灯方向在这个步长内可以被清空不会出现负排队当排队车辆大于饱和流率时排队会残留到下一秒。delay_area累加的是每个时刻所有方向排队之和单位是veh·s也就是延误面积这个值可以在不同配时方案之间做对比。调用时传入一个周期内的绿灯时长字典。注意cycle参数目前只用于约束检查后面分配绿信比时会用到。如果想跑多个周期就把上一个周期的返回queue作为下一个周期的initial_queue这样能观察排队是否会达到动态平衡。3.3 绿信比分配把排队长度变成绿灯时间定时控制是每个周期都按同一张绿信比表走实时控制则要根据当前状态每周期重算。这里我给出一个最简单也最可解释的分配方法按各相位当前排队车辆数占总排队的比例分配可用绿灯时间同时强制满足最小和最大绿灯约束def allocate_greens(queue, cycle, loss_times, min_green10, max_green60): # 扣除损失时间后真正可以分配给各相位的绿灯时间 total_loss sum(loss_times.values()) usable cycle - total_loss q_total sum(queue.values()) if q_total 0: # 没有排队时不能给0绿灯安全起见给最小绿灯 raw {ph: min_green for ph in queue} else: # 按排队比例分配可用时间 raw {ph: usable * queue[ph] / q_total for ph in queue} # 限制在最小和最大绿灯之间 clipped {ph: max(min_green, min(max_green, raw[ph])) for ph in queue} # 如果剪辑后总时长超过可用时间按比例压回去 total_clipped sum(clipped.values()) if total_clipped usable: scale usable / total_clipped greens {ph: clipped[ph] * scale for ph in queue} # 压缩后可能又低于最小绿灯此时说明周期余量不足 # 常见处理是延长周期或者允许短时超时并记录违规 greens {ph: max(min_green, greens[ph]) for ph in queue} else: greens clipped return greens这个函数输出的greens就是下一周期要执行的绿灯时长。它的优点是逻辑透明现场调试时能说清楚“为什么给了南北方向35秒”因为南北排队占比就是那么大。缺点是它只看了当前时刻排队没有预测未来到达。如果某个方向排队在10秒后就会自然消散算法仍然会给它大量绿灯造成空放。要修正空放一种常见做法是给每个周期设置“最小空放检测”当某方向排队已经在连续两个步长内降到0就提前结束该相位把剩余时间转给下一相位或让周期提前结束。竞赛论文里很多优秀方案都在这个细节上做文章但实际落地时我建议先把固定周期版本跑顺再加动态提前结束逻辑。另一个工程折中是低频调整每个周期用检测到的排队长度算一次绿灯但限制每次调整幅度不超过6秒避免两个连续周期的绿灯时长来回跳。4. 参数别乱调信号实时控制最关键的三个参数4.1 周期时长C实时调整的边界在哪周期时长C决定了每个方向红灯等待的上限也决定了通行能力。C太短每天的损失时间占比太高C太长某一个方向的延误会线性上升。实时控制并不意味周期可以随意变化我一般会把周期限制在Webster公式计算值的上下20%范围内。比如刚才例子算出来82秒那么周期只允许在65到98秒之间浮动超出这个范围就维持上一周期的取值。周期对结果的影响可以从损失时间占比理解。若周期40秒损失时间6秒损失占比15%若周期100秒损失占比只有6%。看起来大周期更高效但大周期会拉长红灯等待导致排队出现“整波到达、整波放行”的脉冲现象。实际路口的周期调整还需要和相邻路口协调单独缩短某个路口周期可能让上游路口绿灯末端放出的车流正好撞上本路口红灯形成连锁停车。所以周期不宜每周期都动建议每5分钟或每10分钟调整一次。4.2 饱和流率S理论与现实的偏差饱和流率是排队模型的“放水口径”1800 veh/h只是教科书默认值。车道宽度3.5米以下、有路边停车、大型车混入、上下坡、雨天湿滑都会让实际饱和流率下降。我在模拟项目X里遇到过一件事把某路口的饱和流率按1800代入算法算出来的绿灯时间总是偏短排队在高峰期间不断累积后来实测发现该车道的饱和流率只有1500因为公交站离停车线太近公交车停靠时直接堵掉一条车道。校准饱和流率不需要专业设备。在绿灯启亮后统计连续通过停车线的车头时距去掉前两辆启动延迟的数据取稳定段的平均值再用3600除以平均车头时距就得到该车道的实际饱和流率。注意要分车道统计左转车道和直行车道的饱和流率差别很大。另一个容易被忽略的点是饱和流率还会随时间变化晚高峰比早高峰低雨天比晴天低。实时控制里最好按天气和工作日/节假日分别标定一组参数不要一组参数跑一年。4.3 最小绿灯时间安全约束是隐形天花板最小绿灯时间不是算法参数是安全底线。它取决于行人过街时间和车辆清空时间行人过街需要的最小绿灯约等于过街距离除以行人步行速度老年人多的区域步行速度要按1.0 m/s甚至更低来算左转车辆清空还需要黄灯前已经进入路口但还没通过的车辆驶离。这个约束直接卡死了实时算法的优化空间。举例来说一条双向六车道的路过街距离约21米按1.2 m/s步行速度算需要17.5秒加上绿灯启亮后行人反应时间最小绿灯至少20秒。如果算法算出来该相位只需要12秒就能放空排队那也必须给满20秒多出来的8秒就造成空放。这是实时控制里最无奈的一类空放它不是算法缺陷而是交通法规和人因工程强制的开销。做方案时要把这部分时间列为固定损失不要在验收时把它算作算法失败。下面是三个参数的调试对照表实际排障时按这个顺序查参数推荐范围设定依据过大后果过小后果周期时长CWebster值±20%流量比与损失时间红灯延误增大脉冲到达损失占比高通行能力下降饱和流率S实测值±5%车头时距标定绿灯时间不足排队持续增长绿灯空放周期被浪费最小绿灯G_min20秒以上行人过街与清空时间空放增加周期延长行人安全风险清空不彻底5. 实时信号控制避坑指南五条血泪经验5.1 检测器数据毛刺导致绿灯时长来回跳现象前后两个周期的绿灯时长差距超过15秒路口整体通行节奏混乱驾驶员刚习惯原来配时就被打断。原因检测器原始数据含大量毛刺比如视频检测在强光或阴影下会把一辆车识别成多辆车或者线圈检测在车辆缓慢通过时产生重复触发。直接用原始排队长度参与分配算子会把一个异常峰值当作真实需求。解决进入分配函数之前先把排队序列做指数平滑。我常用的系数是0.3到0.4也就是当前值占三成历史平滑值占七成。另一个办法是采用周期中段采样在绿灯结束前5秒读取排队避开刚变灯时的不稳定读数。平滑会损失一点响应速度但对真实路口的收益远大于损失。如果发现毛刺仍然明显检查检测器安装位置和阈值配置这是数据质量问题算法救不回来。5.2 排队长度估计偏大原因是时间窗没对齐现象算法认为南北方向排队已经溢到上游路口实际监控画面里排队不到20米导致南北相位被连续给了好几个长绿灯东西方向等得怨声载道。原因排队检测时刻和信号状态没对齐。如果排队检测点设在停车线上游80米处车辆正好在红灯期间排队那么读取到的排队包含了本周期刚到达的车这些车本来就不可能立刻走把它当成“压车”是错误的。解决把采样时刻统一到绿灯结束前最后一个步长这时候排队反映的是本周期未被放行的残留需求最能代表下一个周期的分配依据。如果是视频检测还要注意检测区域是否包含对向待转区待转区的车会被误判成方向排队。这个问题在竞赛仿真里几乎不会出现因为仿真里读取的是真值而现场检测器有布设位置误差。5.3 仿真效果很好现场却排队溢出现象同一套参数在仿真里延误下降20%到了真实路口反而把排队溢回上游相邻路口的通行能力被倒灌的车流锁死。原因仿真里的排队长度没有上限车辆可以无限排在任何位置真实路口的排队受路段长度限制溢出后会把上游路口或相邻车道的出口堵死。很多竞赛论文并没有建模这个容量约束因为题目没有要求但落地时这恰恰是最致命的一环。解决给每个方向的排队模型增加容量上限溢出部分直接计入惩罚。比如某路段只能容纳40辆车排队超过40辆时本路口算法必须强制增加该方向绿灯哪怕其他方向延误上升。更进阶的做法是让溢出的方向向协调控制模块发信号触发上游路口截流。没有容量约束的排队模型只能作为理论研究不能直接当现场方案。5.4 相位切换时清空时间不足造成二次排队现象左转绿灯结束后还有两三辆左转车滞留在路口中央对向直行已亮绿灯两股车流在路口内互相干扰整个路口效率骤降。原因实时控制为了省时间把黄灯和全红时间压缩到接近0。黄灯不是可自由压缩的损失时间它是给“已经越过停车线但还没通过路口”的车辆的安全清空期。压缩清空时间省下两三秒代价是路口内部的交通冲突。解决把清空时间固定为常量完全不参与优化。每个相位切换之前检查该相位是否已经清空可以在绿灯末尾加一个“滑窗判断”如果最后3秒的通过流量明显下降再决定是否提前结束。这个逻辑不压缩清空时间只压缩有效绿灯的尾部空放段安全上没问题。血的教训是不要为了仿真里的延误数字去动全红时间除非你想在验收现场看追尾。5.5 用了延误目标优化结果反而更卡现象算法报告平均延误下降了但路口总体通过量也下降了排队总长度没有明显改善驾驶员体感更差。原因延误目标天然倾向于“把已有的排队先清空”因为它保护的是排队面积大的方向。如果某个方向排队时间久但车辆少延误积分也会很大算法会优先给它放行结果放空后该方向接下来根本没有车形成空放真正有持续车流的另一个方向却被压住通过量自然下降。解决把目标里加入“通过量”或“放空惩罚”。我常用的做法是在分配绿信比时先留出8%的可用时间给绝对主流量方向再对剩余时间做延误优化。这样既保留延误目标的好处又避免空放型振荡。验收时不要只看延误要同时看通过量和排队溢出次数三个指标一起对比才有说服力。竞赛里只看延误的排名没问题工程现场要看的是运行稳定性。6. 从竞赛论文到现场方案验证与进阶技巧竞赛论文和现场方案的差距主要在验证方法上。拿到一个实时信号控制算法我建议先做固定周期对照实验选同一路口、同一时段、同一方向的车流数据前一周固定配时后一周实时配时其他条件尽量不变。对比指标选三个平均延误、路口总通过量、排队溢出次数。只报延误下降是危险的因为延误指标可以被空放策略“优化”得很漂亮。验证时还要注意样本量。高峰期交通流每天都不一样至少要取10个工作日的数据做配对比再用非参数检验看差异是否显著。我见过有人拿了三天数据就下结论结果第四天一个雨天全部翻盘。另一个习惯是先做“零基线”把实时控制算法的调整幅度调成0让它完全按照原固定配时运行确认代码本身不会引入额外排队损耗。这一步能帮你把数据采集问题和算法问题分开。进阶方向上这套排队模型可以从单路口扩展到相邻两三个路口把下游路口的排队长度作为上游路口的配时输入协调两条路的相位差。更实际的应用是给公交车一个权重系数在公交车接近路口时动态延长绿灯这是竞赛里“多目标”的自然延伸。但要注意所有进阶都建立在状态检测稳定、基础参数校准完毕的前提下不要在数据没做平滑时就去调公交优先否则你会分不清是算法的问题还是数据的问题。希望这套从排队模型到最小实现的路径能帮到你至少在下一次面对信号控制问题时你知道先看哪个参数出了状况先查哪个环节。本文还有配套的精品资源点击获取