简介:城市交通网络平衡分析是交通工程与城市规划领域的重要议题,黄海军所著《城市交通网络平衡分析理论与实践》从网络拓扑结构、交通流特性、出行时间、运行速度与道路容量五个维度切入,系统阐述平衡分析的核心理论,并结合交通监控、模拟仿真、优化算法、信息化系统与规划设计等典型实践场景,给出城市路网状态评估与拥堵治理的系统性方法。PDF压缩包内共含1个文件,体积约8.1MB,内容层次清晰,既适合交通工程、轨道交通、城市规划专业的高年级学生和研究者,也适合从事智能交通管理与交通模型建设的从业者阅读。目前已有608人学习下载,读者可借此理解城市交通网络平衡分析的完整流程,掌握从数据获取、模型构建到方案比选与效果评价的关键技能;书中还涉及交通监控系统与仿真软件的联动分析,有助于将抽象模型转化为可落地的评估与优化流程,为解决实际交通问题提供理论工具与实践参照。
1. 城市交通网络平衡分析:这本书解决的从来不是画路网,而是算均衡
早高峰开车上路,导航推荐走A路,但A路通畅了十分钟之后也开始排队,这时候你发现B路更快——这种体验背后,就是交通网络平衡分析在起作用。黄海军老师的《城市交通网络平衡分析理论与实践》,讲的不是怎么画路网、不是怎么装信号灯,而是回答一个核心问题:给定路网结构和出行需求,流量最终会稳定在哪个状态,每条路会承载多少车,会不会超过承载力。书里把这套理论从Wardrop均衡、BPR阻抗函数一路推到实际配流算法,城市道路和轨道交通线网都能套用。适合正在做交通规划、轨道交通评估、智能交通系统方案比选,又不想只当软件黑匣子操作员的工程师。
2. 理论骨架:BPR阻抗、Wardrop原理与用户均衡模型的数学结构
很多人一听到“平衡分析”就以为是一堆迭代公式,如果只是用TransCAD跑一遍配流,确实可以不看理论。但一旦结果不对,或者被甲方问“为什么这条路流量这么高、那条路没人走”,不懂理论的劣势就暴露了。这章把模型结构说清楚,也讲明白为什么行业里最终普遍选择了这套组合。
2.1 从道路阻抗到BPR函数:模型为什么选这个形式
交通网络里每条路段都有一个“阻抗”,通俗说就是走这条路需要付出的时间成本。最简单的阻抗是零流时间,也就是路上没车时跑完这段路用的时间。但真实的道路不是这样的:车越多,车速越低,单位里程耗时越长,排队甚至会导致路段时间成倍增加。
业界最常用的阻抗函数是BPR函数,出自美国公路局(Bureau of Public Roads):
t_a(x) = t0_a * (1 + α * (x_a / C_a)^β)
其中t0_a是自由流时间,C_a是路段通行能力,x_a是当前流量。α和β是标定参数,工程默认α=0.15、β=4,这是几十年的经验值,后来很多城市在标定自己路网时也会调整。
为什么不用简单的线性函数?因为道路拥堵是非线性的:流量只有设计能力的一半时,几乎感觉不到变慢;一旦流量接近甚至超过容量,速度会急剧恶化。BPR的幂函数形状正好刻画这种现象。α决定拥堵在整体时间里的权重,β决定“接近饱和时时间如何爆炸式增长”。实际工程里我也见过用α=0.2、β=4的,差别不太大,但如果β取到6甚至更高,模型会对超容量路段反应过激,轻微超载就报出几十倍延误,这时候就要怀疑是不是参数没标定好。
| 参数 | 含义 | 常用取值 | 取值偏大 | 取值偏小 |
|---|---|---|---|---|
| α | 拥堵项权重 | 0.15~0.2 | 拥堵被过度放大 | 拥堵被低估 |
| β | 拥堵增长指数 | 2.5~4 | 接近容量时时间剧烈增长 | 流量影响过于平缓 |
| t0_a | 自由流时间 | 实测/标定 | 整体阻抗偏大 | 整体阻抗偏小 |
| C_a | 路段通行能力 | pcu/h标定 | 过早拥堵 | 拥堵出现太晚 |
这个函数的选型理由,一句话概括:它用最少的参数抓住了“容量约束”这个交通网络最核心的物理特性。理解BPR是理解整个平衡模型的前提,因为后面的用户均衡目标函数、配流算法,全部建立在这个阻抗函数之上。
2.2 Wardrop第一原理与用户均衡模型:公式、约束与最优性条件
有了路段阻抗,接下来要回答的问题是:这么多出行者,各自选择路径,最终会形成什么样的稳定状态。这里最经典的答案是Wardrop第一原理,也叫用户均衡(User Equilibrium,简称UE):当路网达到平衡时,同一OD对之间所有被使用的路径,时间相等且最小;没有被使用的路径,时间大于等于这个最小值。
这个原理的直观感觉是:只要有一条路径比当前走的路径更省时间,就一定会有人换过去;换的人多了,那条路径变慢,直到两边时间一样。每个人都在试图优化自己的出行成本,最终形成了谁都没动机单独改变路径的状态。
把这个问题写成数学规划,就是Beckmann变换:
min Z(x) = Σ_a ∫0^xa t_a(w) dw
约束条件:每个OD对的路径流量之和等于OD需求量;路径流量非负;路段流量等于经过该路段的路径流量之和。
这个目标函数没有物理含义,它只是构造出来的一个数学工具,用来把均衡条件转化为最优化问题。对目标函数求导,结合约束条件做拉格朗日变换,一阶条件正好等价于Wardrop第一原理。这也是后面Frank-Wolfe算法能用的根本原因——先构造一个可解的凸优化问题,再用迭代算法逼近它的最优解。
需要特别注意的是,用户均衡不等于系统最优。Wardrop第二原理(系统最优,SO)对应的是另一个目标函数,追求的是全网总时间最小。如果按SO配流,为了让整体效率最高,可能要求一部分人绕路,让某些路段不要那么堵。工程上两者都有应用,但UE描述的是“自由选择下的现实稳定态”,SO描述的是“管理者希望实现的理想态”。做现状评估用UE,做管控方案优化时可以引入SO做对比,这个边界要分清楚。
2.3 随机用户均衡与弹性需求:现实世界的两个补充
UE模型有个强假设:每个出行者完全理性,且知道全网所有路径的准确时间。现实中显然不是这样,驾驶员对路况的感知有误差,有人不熟悉捷径,有人对拥堵的忍受程度不同。
处理这个问题的方法是随机用户均衡(SUE)。SUE假设每个出行者对路径时间的感知有一个随机误差项,通常假设误差服从Gumbel分布,那么路径选择的概率就变成Logit形式:
P_k = exp(-θ * t_k) / Σ_j exp(-θ * t_j)
这里的θ是感知精度参数。θ越大,出行者对路径时间越敏感,结果越接近确定性UE;θ越小,选择越分散,即使某条路明显更慢也会有人选。这个模型在实践里很有用,因为现实路网中很少出现“所有被选路径时间完全相等”的干净结果,SUE能保留一部分分散性,让分配结果更贴近实测。
弹性需求则是另一个方向的扩展:UE和SUE都假设OD需求固定,但现实里如果某条通勤走廊特别堵,有些人可能不开车了,改坐轨道交通,或者干脆错峰出行。弹性需求模型把OD量变成出行成本的函数,成本越高,需求量越低。这个扩展在城市交通与轨道交通联合分配时非常关键——地面太堵,客流就会向轨道转移,这种跨方式转移恰恰需要一个弹性需求框架才能描述。
到这里理论框架基本立住了:BPR负责描述路段阻抗,UE提供均衡的数学刻画,SUE处理出行者感知误差,弹性需求处理需求端反馈。下一章把这套东西落到纸面上,手算一个例子,再写一段能直接跑的配流代码。
3. 实践入门:手算双路径均衡与Python写Frank-Wolfe配流
理论看懂了,不亲手算一遍等于没学。这章用一个最简单的双路径网络演示什么叫“均衡”,然后给出一个可以直接跑通的Python配流脚本,最后说明怎么把它接到真实路网上。
3.1 手算一个双路径均衡问题:流量不是均分,时间是均分的
考虑从A到B有两条平行路径,需求总量是1500辆车。路径1自由流时间10分钟,容量小所以时间随流量增长快;路径2自由流时间12分钟,容量大,增长慢。用线性阻抗函数近似:
t1 = 10 + f1 / 200 t2 = 12 + f2 / 500
f1和f2分别是两条路径的流量,满足f1+f2=1500。均衡条件是:两条路径被同时使用,且时间相等。
t1 = t2 10 + f1/200 = 12 + f2/500
联立守恒方程求解:
f1 ≈ 714,f2 ≈ 786,t1 = t2 ≈ 13.57分钟
注意这个结果:路径1自由流快,但容量小,最终并没有吃掉全部流量,而是承担了714辆;路径2虽然起点更慢,但容量大,反而承担了更多。很多人第一次做这个手算时会下意识认为“均衡=流量平均分”,这是错的。平衡分的是“时间”,不是“流量”。哪条路被用到什么程度,取决于它的阻抗曲线和容量特性,而不是机械地一人一半。
这一步手算的价值在于:它把UE的“动机”彻底讲透了。如果初始时刻所有车都走路径1,路径1会变成14.5分钟,路径2还是12分钟,马上有人换过去。换到一定程度,两边时间一样,没有人再单独行动能获益,网络就稳定了。这就是Wardrop第一原理在一个具体数字上的体现。
3.2 用Python实现Frank-Wolfe配流:教学版代码与参数说明
手算只能处理两条平行路径,真实路网是几十上百条路段交织的网络,必须用迭代算法。最常见的算法是Frank-Wolfe,思路很简单:每次在“当前路况”下做一次全有全无分配(把所有流量压到最短路径上),得到一个方向,然后用线性搜索找一个最优步长,往这个方向走一步,更新流量,反复迭代到收敛。
下面这段代码是教学版,直接在本地跑即可,需要安装numpy:
import numpy as np # 三路段网络: # 路段0: A->B 直达 # 路段1: A->C # 路段2: C->B links = { 0: {'t0': 10.0, 'cap': 1000.0}, 1: {'t0': 8.0, 'cap': 800.0}, 2: {'t0': 9.0, 'cap': 1200.0}, } # 两条候选路径:直达 和 经过C中转 routes = { 'direct': [0], 'via_C': [1, 2], } demand = 1800.0 # OD需求:从A到B alpha = 0.15 # BPR拥堵权重 beta = 4.0 # BPR指数 def travel_time(flow, link): # BPR函数 return link['t0'] * (1 + alpha * (flow / link['cap']) ** beta) def path_to_link_flow(f_r): # 把路径流量转换为路段流量 x_a = np.zeros(len(links)) for r_idx, (r_name, link_list) in enumerate(routes.items()): for a in link_list: x_a[a] += f_r[r_idx] return x_a def route_times(f_r): # 由路段流量计算路径时间 x_a = path_to_link_flow(f_r) t_a = np.array([travel_time(x_a[a], links[a]) for a in links]) t_r = [] for r_name, link_list in routes.items(): t_r.append(sum(t_a[a] for a in link_list)) return np.array(t_r) def all_or_nothing(t_r): # 全有全无:把需求全部给最短路径 y_r = np.zeros(len(t_r)) y_r[int(np.argmin(t_r))] = demand return y_r def objective(f_r): # Beckmann目标函数值,用于线性搜索 x_a = path_to_link_flow(f_r) total = 0.0 for a in links: link = links[a] f = x_a[a] total += (link['t0'] * f + link['t0'] * alpha * f ** (beta + 1) / (link['cap'] ** beta * (beta + 1))) return total # 初始化路径流量为0 f_r = np.zeros(len(routes)) for it in range(100): t_r = route_times(f_r) y_r = all_or_nothing(t_r) # 线性搜索最优步长:网格搜索0~1 best_step, best_obj = 0.0, float('inf') for k in range(101): step = k / 100.0 candidate = f_r + step * (y_r - f_r) obj_val = objective(candidate) if obj_val < best_obj: best_obj, best_step = obj_val, step f_r = f_r + best_step * (y_r - f_r) # 相对Gap:衡量当前解的收敛程度 gap = abs(np.dot(y_r - f_r, t_r)) / np.dot(f_r, t_r) if it % 10 == 0: print(f"iter={it:3d}, step={best_step:.4f}, rel_gap={gap:.6f}") print(" 路径流量:", np.round(f_r, 2), " 路段流量:", np.round(path_to_link_flow(f_r), 2)) if gap < 1e-6: print("已收敛") break代码逻辑分四步走:
- 当前流量下用BPR计算每条路段的行驶时间,再累加成每个路径的时间;
- 全有全无分配,把1800辆车全部放到“当前最短路径”上,得到一个方向向量;
- 用网格线搜索从当前流量向目标方向移动多少步,这是Frank-Wolfe里计算量比较大的一步;
- 更新路径流量,计算相对Gap判断是否已经收敛。
参数上,demand、alpha、beta、各路段t0和cap都是可调的。alpha=0.15和beta=4是BPR默认值,想模拟更激烈拥堵可以调到0.2和4.5。网格搜索步长我直接用0.01粒度,省去了scipy依赖,精确线性搜索在实际项目中可以用scipy.optimize.minimize_scalar替代。收敛判据相对Gap取1e-6,工程上一般1e-3到1e-4就够了,太小的Gap会明显增加迭代次数。
这个网络很典型:两条路径共享一部分路段的时候,流量会在路径间来回摆动,迭代初期你会看到路径流量大幅跳动,这不是代码bug,是Frank-Wolfe的特性,后期会逐渐回落稳定。
3.3 把脚本接到真实路网:从OD矩阵到边表、路径表与软件对照
教学版用三路段、两条路径,真实路网不会是手工枚举路径。工程项目的标准输入是三张表:路网表、节点表、OD矩阵。
| 表名 | 关键字段 | 说明 |
|---|---|---|
| 路网表 | 起终点节点、自由流时间、通行能力、道路等级 | 每条有向路段一行 |
| 节点表 | 节点编号、经纬度、是否小区(交通小区质心) | 用来连接OD与路网 |
| OD矩阵 | 起点小区、终点小区、出行量 | 每个小区对一行 |
拿到这三张表后,做法通常是用TransCAD或EMME这类交通规划软件做配流,它们内部处理了路径枚举、增量加载这些事,比自研脚本省力得多。但自研脚本有一个好处:你可以精确控制BPR参数、换乘惩罚、多方式网络接驳这些细节,这在轨道交通+地面公交组合网络里特别有用,因为商业软件对换乘阻抗的设置往往不如自己改代码灵活。
从小网络往大网络切换时,推荐路线是:先用Python处理OD和路网数据,生成标准的路网表和OD矩阵,用脚本做一遍快速配流看趋势,再用TransCAD精细标定,最后用实测流量反推OD并校验结果。这样两套系统互相印证,比单一依赖某一个工具更稳。
4. 理论落地避坑指南:不收敛、爆容量与数据错位的排查记录
平衡分配模型看着公式简单,实际跑起来坑很多。不少问题不是模型原理错了,而是参数或数据没处理好。这章写几条典型踩坑记录,每一条都是真实项目里反复出现过的。
4.1 前后横跳不收敛:全有全无方向太硬,步长搜索太粗
现象:迭代过程中流量在两条路径之间来回剧烈摆动,比如路径1流量从1800跳到200再跳回1700,Gap始终下不去,最终报错退出。
原因:全有全无分配是“一刀切”的,每次只给最短路径全部流量。如果两条路径时间接近,最短路径会频繁切换,流量就来回横跳。如果线性搜索步长计算不精确,或者用了固定步长,就更难收敛。
解决:精确线性搜索是首选,网格步长至少要0.01;实在不想写线搜,就用MSA(连续平均法),步长取1/n,第n次迭代直接用1/n合并当前方向和旧流量。MSA不需要求目标函数,收敛虽然慢但稳定,特别适合路径流量多、目标函数计算代价大的情况。另外收敛判据不要只依赖迭代次数,要用相对Gap,比如小于1e-3就停下来。
4.2 BPR参数拍脑袋:α、β选错,结果全线失真
现象:模型跑完,某条路的流量明明才到容量的80%,行程时间却已经是自由流的3倍,明显不符合常理;或者反过来,流量已经超过容量,速度还在60km/h以上。
原因:α和β是标定参数,默认0.15和4不是普适的。β=4意味着当流量达到容量的1.2倍时,(1.2)^4=2.07,时间会变成自由流的1.3倍还多。但高速匝道、城市快速路和主干路的拥堵曲线差异很大,快速路拥堵是缓变的,次干路接近容量后恶化非常快。
解决:分道路等级标定参数。快速路常用α=0.1、β=3;主干路α=0.15、β=4;次干路可以到α=0.2、β=5。标定的依据是实测行程时间与流量关系,至少要做几个典型段落的散点拟合,不能什么路网都用一套参数。如果项目周期短,至少要保证所有路段按道路等级分组设置参数,别全网一套数打天下。
4.3 单位混用:分钟、小时、pcu/h没统一,时间结果全错
现象:BPR公式算出来的某路段行程时间比实际大几十倍,或者流量大得离谱,检查发现输入文件里有些路段自由流时间写的是分钟,有些写成小时。
原因:BPR函数对单位没有自动识别能力,自由流时间与容量、流量必须用同一套单位体系。中国工程习惯是时间用分钟、流量用pcu/h,这种情况下t0是分钟,C和x都是pcu/h,时间单位是分钟。如果不想用分钟,也可以全转成小时,但所有路段必须统一。
解决:处理输入数据时先写一个单位校验函数,扫描所有路段的t0字段,值在0到50之间大概率是分钟,大于50可能是小时或者写错了。流量字段要和OD矩阵单位一致,OD单位是万pcu/d,就要先换算成高峰小时流量再输入。这条错是整个项目里最容易出现、也最容易让人怀疑模型没用的问题。
4.4 把OD直接写成路段流量:这是最常犯的数据错误
现象:路网表里某路段的流量字段填的是一堆很大的数,直接当成“现状流量”拿来和分配结果对比,发现曲线完全不匹配。
原因:OD反映的是小区之间的出行需求,路段流量是这些需求经过配流之后的结果,两者不是一回事。有人从交警那边拿到断面流量,转手就当成路段属性填进去,后面做均衡分配时,这个字段又跟路段容量混在一起,BPR公式彻底乱掉。
解决:路网表里只保留自由流时间和通行能力,流量永远是分配算法的输出,不是输入。处理实测断面流量时,单独建一张观测流量表,用相对误差或GEH指标对比分配结果和观测值,不要混在路网表里。
4.5 拿平衡结果硬比瞬时实测:理论输出与实测的对比方法
现象:模型预测某路段高峰小时流量1200,实测雷达数据只有800,差了50%,甲方直接质疑模型无用。
原因:UE模型的输出是“均衡状态下的流量”,它描述的是路网在给定需求下的稳定分布,不是某个瞬时时刻的真实交通流。城市道路受信号配时、公交停靠、偶发事故影响,瞬时波动很大。高峰小时流量也不是一个固定值,不同日期差个10%到20%很正常。
解决:对比时不要用某一天的断面流量,用一个月的均值,而且只选非拥堵时段或稳定时段做校验。更专业的做法是标定OD矩阵时,把观测流量分方向、分时段做约束,让分配结果尽量逼近多个断面的观测值。UE结果的定位是“长期平均的平衡分布”,不是“实时仿真”,对比口径错了,再好的模型也救不回来。
5. 轨道交通场景的具体用法:把线网改成均衡模型,再用满载率定位瓶颈
城市轨道交通和道路网络共享同一个平衡分析框架,对做轨道交通规划的人来说,这本书后半部分的网络模型套用到地铁线网上,可以直接定位拥挤瓶颈。
5.1 把轨道线网改造成平衡分析的有向图
地铁车站是节点,区间是有向边,乘客从进站到出站经过的车站序列就是一条路径。把换乘站做成连接不同线路的虚拟边,网络就完整了。这里的阻抗不再只是“区间行驶时间”,还包括停站时间、起停附加时间和换乘走行时间。
5.2 地铁区间阻抗怎么折算
实际做法是把综合阻抗写成:
区间阻抗 = 区间走行时间 x (1 + 拥挤系数)
走行时间 = 区间长度 / 旅行速度 + 停站时间 + 起停附加时间;换乘走行时间按实际步行距离除以平均速度,再乘以2到3倍的惩罚系数,因为换乘带来的体力消耗和心理成本远高于同等的车内时间。拥挤项修正可以用BPR的形式,但要把参数调大,地面道路α=0.15,地铁上建议α=0.3、β=5,因为车厢站立密度超过一定水平之后,乘客的体验恶化速度远高于道路拥堵。
5.3 用满载率定位瓶颈:从平衡分配结果反推发车间隔
配流完成后,每个区间的断面客流就出来了。满载率 = 断面客流 / 设计运能,设计运能 = 单位小时开行对数 × 列车定员 × 折减系数。满载率超过100%的区间,就是需要缩短发车间隔或者加开区间车的候选位置。
| 示例区间 | 断面客流 | 设计运能 | 满载率 | 处置建议 |
|---|---|---|---|---|
| A站-B站 | 28500 | 24000 | 119% | 缩短发车间隔或加开区间车 |
| B站-C站 | 21000 | 24000 | 88% | 可正常运营 |
| C站-D站 | 26500 | 24000 | 110% | 关注早晚高峰潮汐 |
我自己的习惯是:先用均衡模型跑一遍全网,把满载率超标的区间筛出来,再针对这些区间做微观仿真确认瓶颈细节,而不是直接在所有区间上铺开仿真空耗算力。从那以后我每次做轨道线网评估,都会先跑一遍平衡分配,再下钻仿真,这个顺序省掉了不少返工,希望帮到你。
本文还有配套的精品资源,点击获取