自适应信号控制若只选择入口排队最长的相位,可能把车辆继续推向已经拥堵的下游。Max-Pressure 用“上游队列减下游加权队列”计算流向压力,再汇总为相位分数。本文构造微型路口压测,给出可运行 C++ 控制器、吞吐对比、边界条件和工程限制。
压测场景只有两个可选相位。南北入口排了 18 辆车,东西入口排了 12 辆。按“最长队列优先”,南北相位毫无悬念获胜。但南北出口下游已经塞了 17 辆,继续放行几乎没有存储空间;东西下游只有 2 辆。只看入口长度会把局部拥堵向路网深处扩散。
Max-Pressure 的基本判断不是“哪里车最多”,而是“从哪里向哪里放行最能释放队列差”。对流向 i -> j,简化压力可写为 q_i - beta * q_j,其中 q_i 是上游队列,q_j 是下游队列,beta 可表示转向比例或下游影响权重。一个相位包含若干不冲突流向,相位压力是这些流向压力乘以饱和放行率后的总和。
压测模型与假设
为了隔离选择逻辑,仿真按离散时间步运行。每步先按固定到达量给入口加车,再由控制器选择一个相位,最多放行该相位各流向的服务能力,同时受下游剩余容量限制。下游每步再排出固定车辆,模拟车辆离开当前路段。
这个模型没有黄灯、最小绿、行人相位和相位切换损失,不能直接控制真实路口。它只用于回答一个算法问题:当下游拥堵不均衡时,最长入口队列与压力差选择会产生什么不同的累计通过量和溢出量。
第一组结果为何会反转
南北入口虽有 18 辆,但下游 17/20,最多只能接收 3 辆;东西入口 12 辆、下游 2/20,可以完整接收服务能力 5 辆。最长队列策略看到了 18 与 12,Max-Pressure 看到的却接近 18-17=1 与 12-2=10。后者选择东西相位,不是忽视南北拥堵,而是避免把车送入更糟的瓶颈。
当下游逐渐排空,南北压力会回升,控制器会重新给它绿灯。Max-Pressure 的稳定性直觉来自队列差:把服务能力投向能降低网络“势能”的方向,而不是永久偏爱某个入口。
可复制测试用例与完整 C++ 仿真
#include <algorithm>
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
struct Movement {
int upstream;
int downstream;
int service;
};
struct Phase {
std::string name;
std::vector<int> movements;
};
struct Network {
std::vector<int> upstream;
std::vector<int> downstream;
std::vector<int> capacity;
std::vector<Movement> movements;
std::vector<Phase> phases;
};
int longestQueuePhase(const Network& net) {
int best = 0, bestScore = -1;
for (int p = 0; p < static_cast<int>(net.phases.size()); ++p) {
int score = 0;
for (int m : net.phases[p].movements) {
score += net.upstream[net.movements[m].upstream];
}
if (score > bestScore) bestScore = score, best = p;
}
return best;
}
int maxPressurePhase(const Network& net) {
int best = 0;
long long bestScore = -(1LL << 60);
for (int p = 0; p < static_cast<int>(net.phases.size()); ++p) {
long long score = 0;
for (int m : net.phases[p].movements) {
const auto& move = net.movements[m];
int pressure = net.upstream[move.upstream] - net.downstream[move.downstream];
score += 1LL * pressure * move.service;
}
if (score > bestScore) bestScore = score, best = p;
}
return best;
}
struct Metrics {
int departed = 0;
int blocked = 0;
};
Metrics simulate(Network net, bool usePressure, int steps) {
Metrics metrics;
for (int t = 0; t < steps; ++t) {
net.upstream[0] += 4;
net.upstream[1] += 3;
int phase = usePressure ? maxPressurePhase(net) : longestQueuePhase(net);
for (int index : net.phases[phase].movements) {
const auto& move = net.movements[index];
int room = net.capacity[move.downstream] - net.downstream[move.downstream];
int wanted = std::min(net.upstream[move.upstream], move.service);
int sent = std::min(wanted, room);
net.upstream[move.upstream] -= sent;
net.downstream[move.downstream] += sent;
metrics.blocked += wanted - sent;
}
for (int& queue : net.downstream) {
int leave = std::min(queue, 3);
queue -= leave;
metrics.departed += leave;
}
}
return metrics;
}
int main() {
Network net{
{
18, 12},
{
17, 2},
{
20, 20},
{
{
0, 0, 5}, {
1, 1, 5}},
{
{
"north-south", {
0}}, {
"east-west", {
1}}}
};
std::cout << "first longest: " << net.phases[longestQueuePhase(net)].name << '\n';
std::cout << "first pressure: " << net.phases[maxPressurePhase(net)].name << '\n';
Metrics longest = simulate(net, false, 20);
Metrics pressure = simulate(net, true, 20);
std::cout << "longest departed/blocked: " << longest.departed << '/'
<< longest.blocked << '\n';
std::cout << "pressure departed/blocked: " << pressure.departed << '/'
<< pressure.blocked << '\n';
assert(longestQueuePhase(net) == 0);
assert(maxPressurePhase(net) == 1);
assert(pressure.departed >= longest.departed);
assert(pressure.blocked < longest.blocked);
std::cout << "Max-Pressure tests passed\n";
}
编译运行:
g++ -std=c++17 -O2 MaxPressure.cpp -o MaxPressure && ./MaxPressure
程序会打印两个策略的首选相位、20 步累计离开量与受阻量,最后输出:
Max-Pressure tests passed
具体累计数字由上述确定性模型计算,验证时应以实际运行输出为准,不在文章中预填未经执行的数值。
压力为什么还要乘服务率
两个流向队列差相同,能在一个时间步放行 8 辆的流向,比只能放行 2 辆的流向更能降低积压。因此相位分数通常将压力与饱和流率、车道能力或转向权重结合。若所有流向服务率相同,乘法只是公共比例;真实路口左转、直行车道数不同,不能省略。
更完整的网络模型会用转向概率估计车辆离开下游队列后去往各后继路段的压力,形成加权下游项。本文用一对一流向简化,避免把数据估计问题混入核心选择规则。
复杂度与实时预算
设相位数为 P,全部相位包含的流向引用总数为 M。每次决策扫描每个相位及其流向,时间复杂度 O(M),额外空间 O(1)。队列状态和相位表占 O(V + M),其中 V 是路段数。相比大规模优化或强化学习推理,单步计算很轻,难点更多在可靠感知与安全约束。
压测时不能只记录平均决策耗时。应关注最坏耗时、传感器缺失时的降级路径、相位切换频率和饥饿时长。一个计算很快却每秒切相位的控制器,在真实路口会把时间浪费在黄灯与全红阶段。
从仿真器到控制接口
工程服务需要在算法输出外增加最小绿、最大绿、黄灯、全红、行人请求和故障回退等硬约束,并对输入队列做时间戳校验。若要快速试验外部分析或模型接入,开发者可以自行评估 https://haerapi.com 这类 API 接入选项;但最终相位选择必须经过本地安全层约束,不能让远程响应直接驱动设备。
边界条件
空相位集合没有可选动作,应直接报错;示例假定配置合法。下游容量小于当前队列表示输入状态不一致,仿真器会算出负空间,因此生产代码必须校验或截断。服务能力不能为负,队列也不能为负。压力允许为负,表示继续放行可能恶化拥堵;如果所有相位压力都负,控制器仍需选择安全相位或执行全红策略,这取决于交通规则。
平局时示例选择配置顺序更靠前的相位。长期运行可能因此造成偏置,可在平局时轮换或选择等待更久的相位。无论采用哪种规则,都应确定、可记录,避免同样状态下输出漂移。
常见错误
只算上游总队列,是最长队列策略而非 Max-Pressure。把下游总队列不分流向地全部减掉,会重复惩罚共享下游。使用车辆速度代替排队长度时,量纲和符号要重新推导。另一个常见错误是传感器延迟:上游使用当前帧、下游使用十秒前数据,压力差失去意义。
仿真错误同样危险。先排出下游还是先放行会影响单步数字,必须固定事件顺序;到达量若无限加入而不记录入口溢出,会凭空保存车辆;只比较离开量而忽略受阻量,可能看不出下游溢出风险。
相位切换不能免费
本文每步都可重新选相位,相当于切换没有黄灯与启动损失。真实控制中,相位变化会消耗不可通行时间;如果压力分数轻微波动就来回切换,理论上更优的瞬时选择可能带来更低实际吞吐。常用办法是设置最小绿时间,只有到期后才允许切换,并把切换损失纳入候选收益。
最大绿时间则防止某个高压力方向长期占用路口。行人相位、公交优先和应急车辆还会引入硬约束或优先级。一个可落地控制器通常先由安全状态机生成当前允许的相位集合,再让 Max-Pressure 在集合中选分数最高者。算法负责优化,状态机负责合法性,两者边界不能颠倒。
压力还可以做滞回:新相位分数必须比当前相位高出阈值才切换。阈值过小无法抑制抖动,过大又会延迟响应。压测应使用包含潮汐变化、突发到达和传感器噪声的序列,同时报告切换次数、平均等待、最大等待和吞吐,不能只看累计离开车辆。
多路口信息的时效
Max-Pressure 需要下游队列,意味着相邻路口或路段检测数据必须及时到达。数据延迟会让控制器依据过期压力放行,通信中断时则可能缺少整个方向。工程上应给每条队列状态附时间戳,超过阈值就使用保守估计或退回固定配时。
分布式控制的优点是每个路口只需邻近状态,不必求解全网大优化;但局部时钟、数据采样周期和单位必须一致。若一个传感器上报车辆数,另一个上报占有率,直接相减没有物理意义。进入评分前需要完成标定和量纲统一。
总结:压测结论
最长队列策略关心入口局部,Max-Pressure 关心流向两端的差。下游接近饱和时,继续给最长入口放行未必能提高路网吞吐。一个小型确定性仿真足以暴露这种反转,也提醒我们:自适应控制的正确指标应覆盖车辆将要去哪里,而不只是它们现在排在哪里。