交通规划四法:弗兰特、底特律、平均系数与佛尼斯法详解

发布时间:2026/10/12 21:14:34
交通规划四法:弗兰特、底特律、平均系数与佛尼斯法详解
简介这份资源面向交通规划课程学习者与课后习题编程需求者聚焦交通量生成与预测这一核心环节提供弗兰特法、底特律法、平均系数法与佛尼斯法四种经典方法的MATLAB实现。压缩包共4个文件均为m脚本整体约2KB分别对应各方法的算法流程弗兰特法基于历史数据做线性预测底特律法引入道路饱和度调整增长率平均系数法统计历史平均增长系数佛尼斯法则通过路网建模与迭代分配求解路段流量。目前已有928人学习下载说明其在课程作业与自学场景中具有一定参考价值。读者可直接运行脚本理解各方法的计算逻辑通过修改参数比较不同模型的预测结果进而掌握如何依据区域发展特征与路网结构选择合适的交通量预测方法也可在此基础上扩展多因素影响下的交通量变化分析。1. 交通规划里的四把“老算盘”弗兰特法、底特律法、平均系数法、佛尼斯法到底在算什么做交通需求预测的人绕不开出行分布这一环。你手里有一张现状OD表又通过出行生成算出了各小区的未来发生量和吸引量接下来要回答的问题很具体未来这些出行到底从哪个小区流向哪个小区弗兰特法、底特律法、平均系数法、佛尼斯法就是回答这个问题的四种经典增长系数法。它们不需要复杂的效用函数标定只靠现状OD矩阵和未来控制总量就能迭代出一张收敛的未来OD表。适合谁交通规划专业的学生做课程设计、规划院工程师做快速方案比选、以及任何需要在小样本数据下跑出一版分布预测的人。这四种方法本质上是同一套迭代框架下的四种系数构造方式理解了这一点后面所有参数和坑都好办了。2. 四种增长系数法的数学骨架先搞懂迭代逻辑再动手2.1 增长系数法的统一迭代框架所有增长系数法共享同一个迭代结构。设现状OD矩阵为 $T_{ij}$小区 $i$ 的未来发生量为 $O_i$未来吸引量为 $D_j$。每一轮迭代做三件事计算发生增长系数 $F_{O_i} O_i / o_i$$o_i$ 是当前矩阵的行和计算吸引增长系数 $F_{D_j} D_j / d_j$$d_j$ 是当前矩阵的列和然后用某种方式把这两个系数组合成一个综合系数 $f_{ij}$更新 $T_{ij} \leftarrow T_{ij} \cdot f_{ij}$。重复直到所有 $F_{O_i}$ 和 $F_{D_j}$ 都落在 1±ε 范围内。四种方法的区别只在于 $f_{ij}$ 怎么构造。弗兰特法用 $f_{ij} F_{O_i} \cdot F_{D_j} \cdot \frac{L_i L_j}{2}$其中 $L_i$、$L_j$ 是地区平衡因子底特律法用 $f_{ij} F_{O_i} \cdot F_{D_j} / \sum_j (T_{ij} \cdot F_{D_j})$ 的归一化形式平均系数法最简单$f_{ij} (F_{O_i} F_{D_j})/2$佛尼斯法则交替调整发生和吸引先让行和匹配 $O_i$再让列和匹配 $D_j$不构造综合系数。这个框架的关键在于每次更新后矩阵的总量会变化所以必须重新计算行列和再进入下一轮。很多人第一次写代码时忘了在每轮迭代后重新算 $o_i$ 和 $d_j$导致系数一直不收敛这是最典型的翻车点。2.2 四种方法的系数构造差异与选型理由弗兰特法的核心优势是引入了地区平衡因子 $L_i$。$L_i$ 的迭代公式是 $L_i \frac{o_i}{\sum_j (T_{ij} \cdot F_{D_j} \cdot L_j)}$初始时所有 $L_i 1$。这个因子让方法在发生量和吸引量同时约束时收敛更稳尤其适合发生吸引量差距较大的场景。底特律法在每次迭代中对行系数做了归一化处理收敛速度通常比弗兰特法快但归一化会引入额外的计算量。平均系数法计算最轻适合快速估算但收敛慢且当 $F_{O_i}$ 和 $F_{D_j}$ 差异大时容易震荡。佛尼斯法不构造综合系数而是交替约束逻辑最直观适合教学演示和理解迭代本质但收敛速度取决于发生和吸引的调整顺序。选型建议如果只是做课程作业或快速比选平均系数法够用如果要做正式预测且数据质量一般弗兰特法更稳如果追求收敛速度且能接受稍复杂的实现底特律法是不错的选择佛尼斯法适合用来验证其他方法的收敛结果是否合理。2.3 用 Python 实现四种方法的最小可运行代码下面这段代码用同一个现状OD矩阵和未来控制量分别跑四种方法输出迭代次数和最终误差。代码里所有参数都有注释可以直接复制运行。import numpy as np # 现状OD矩阵4个小区 T np.array([ [50, 30, 20, 10], [25, 60, 15, 20], [15, 20, 55, 30], [10, 25, 35, 70] ], dtypefloat) # 未来发生量O和吸引量D O np.array([130, 140, 135, 155], dtypefloat) D np.array([110, 150, 140, 160], dtypefloat) EPS 0.01 # 收敛阈值 MAX_ITER 200 # 最大迭代次数 def check_converge(T, O, D, eps): 检查行列增长系数是否都接近1 row_sum T.sum(axis1) col_sum T.sum(axis0) FO O / row_sum FD D / col_sum return np.all(np.abs(FO - 1) eps) and np.all(np.abs(FD - 1) eps) def furness(T, O, D, epsEPS, max_iterMAX_ITER): 佛尼斯法交替调整行和列 T T.copy() for it in range(max_iter): # 先调行 row_sum T.sum(axis1) T * (O / row_sum)[:, None] # 再调列 col_sum T.sum(axis0) T * (D / col_sum)[None, :] if check_converge(T, O, D, eps): return T, it 1 return T, max_iter def average_factor(T, O, D, epsEPS, max_iterMAX_ITER): 平均系数法 T T.copy() for it in range(max_iter): row_sum T.sum(axis1) col_sum T.sum(axis0) FO O / row_sum FD D / col_sum F (FO[:, None] FD[None, :]) / 2.0 T * F if check_converge(T, O, D, eps): return T, it 1 return T, max_iter def detroit(T, O, D, epsEPS, max_iterMAX_ITER): 底特律法 T T.copy() for it in range(max_iter): row_sum T.sum(axis1) col_sum T.sum(axis0) FO O / row_sum FD D / col_sum # 底特律法先按吸引系数调整再按行归一化 T * FD[None, :] row_sum_new T.sum(axis1) T * (O / row_sum_new)[:, None] if check_converge(T, O, D, eps): return T, it 1 return T, max_iter def fratar(T, O, D, epsEPS, max_iterMAX_ITER): 弗兰特法 T T.copy() n T.shape[0] L np.ones(n) # 地区平衡因子 for it in range(max_iter): row_sum T.sum(axis1) col_sum T.sum(axis0) FO O / row_sum FD D / col_sum # 更新L因子 for i in range(n): denom np.sum(T[i, :] * FD * L) if denom 0: L[i] row_sum[i] / denom # 综合系数 F np.zeros_like(T) for i in range(n): for j in range(n): F[i, j] FO[i] * FD[j] * (L[i] L[j]) / 2.0 T * F if check_converge(T, O, D, eps): return T, it 1 return T, max_iter # 分别运行四种方法 methods { 佛尼斯法: furness, 平均系数法: average_factor, 底特律法: detroit, 弗兰特法: fratar } for name, func in methods.items(): result, iters func(T, O, D) row_err np.abs(result.sum(axis1) - O).max() col_err np.abs(result.sum(axis0) - D).max() print(f{name}: 迭代{iters}次, 行最大误差{row_err:.4f}, 列最大误差{col_err:.4f})这段代码的逻辑说明check_converge函数是四种方法共用的收敛判断检查所有行列增长系数是否都在 1±ε 内。furness函数先调行再调列交替进行不构造综合系数。average_factor直接取发生和吸引系数的算术平均。detroit先按吸引系数调整再按行归一化这是底特律法的标准形式。fratar维护一个L数组每轮更新后再构造综合系数。参数说明EPS控制收敛精度一般取 0.01 到 0.05取太小会导致迭代次数暴增MAX_ITER是安全上限防止死循环。实际项目中如果迭代超过 50 次还没收敛通常说明控制量本身矛盾需要回头检查出行生成的结果。3. 参数怎么设、迭代怎么调四种方法的实操细节3.1 收敛阈值与迭代上限的取值经验收敛阈值EPS不是越小越好。我一般会先用 0.05 跑一遍看迭代次数如果 20 次内收敛再降到 0.01 跑最终结果。如果 0.05 都要跑 50 次以上说明现状OD矩阵和未来控制量之间的结构性差异太大这时候强行调小阈值只会浪费时间应该先检查出行生成阶段的发生吸引量是否合理。迭代上限MAX_ITER设 200 是保守做法。实际跑下来平均系数法在 4 小区案例中通常 30 到 50 次收敛弗兰特法和底特律法在 15 到 30 次佛尼斯法在 20 到 40 次。如果超过 100 次还没收敛基本可以判定数据有问题不用再等。提示收敛判断要同时检查行和列只检查行和会导致列和偏差被忽略最终OD表的吸引量对不上。3.2 初始OD矩阵的预处理零单元格和量纲问题现状OD矩阵里经常有零单元格比如某两个小区之间现状没有出行。增长系数法对零单元格的处理很敏感如果 $T_{ij}0$无论乘什么系数结果都是零未来这两个小区之间就永远不会有出行。这在实际规划中可能不合理因为未来土地利用变化可能产生新的出行联系。常见做法是给零单元格一个很小的种子值比如 0.1 或 1或者在迭代前用重力模型补一个初始值。我一般会先检查零单元格的比例如果低于 5%直接给 0.1 的种子如果超过 10%说明现状OD调查可能有问题需要先做数据清洗。量纲问题也容易翻车。发生量 $O_i$ 和吸引量 $D_j$ 的单位必须和现状OD矩阵一致都是人次或都是车次。如果现状OD是车次未来控制量是人次迭代结果会完全失真。这个坑在多人协作的项目里特别常见因为出行生成和出行分布可能是不同人做的。3.3 四种方法的收敛速度对比与结果差异用第 2 章的代码跑一遍四种方法的结果会有细微差异。佛尼斯法和底特律法的最终矩阵通常最接近因为两者都做了行列交替约束。平均系数法的结果在发生吸引量差异大的小区上偏差稍大因为算术平均没有考虑行列约束的优先级。弗兰特法因为引入了 $L$ 因子结果通常介于佛尼斯法和平均系数法之间。收敛速度方面底特律法最快因为它每轮都做了行归一化相当于强制行和匹配。弗兰特法次之$L$ 因子加速了收敛。佛尼斯法第三交替调整需要更多轮次。平均系数法最慢因为算术平均对行列约束的修正力度不够。实际项目中如果只是做方案比选四种方法的差异通常在 5% 以内选哪个都行。但如果要做正式预测报告建议至少用两种方法交叉验证结果差异超过 10% 的小区需要重点检查。3.4 用表格对比四种方法的关键参数方法综合系数构造收敛速度适用场景实现复杂度弗兰特法$F_{O_i} \cdot F_{D_j} \cdot (L_iL_j)/2$中等发生吸引量差距大较高底特律法先吸引调整再行归一化快追求收敛速度中等平均系数法$(F_{O_i}F_{D_j})/2$慢快速估算低佛尼斯法交替行列约束中等教学与验证低这张表可以作为选型时的快速参考。注意收敛速度是在 4 小区、EPS0.01 条件下的相对比较小区数量增加后排序基本不变但迭代次数会上升。4. 避坑与排查增长系数法最容易翻车的五个地方4.1 迭代不收敛行列和一直震荡现象跑了几十轮行增长系数和列增长系数在 1 附近来回跳就是不进入阈值范围。原因通常是未来控制量 $O_i$ 和 $D_j$ 的总和不相等。增长系数法要求 $\sum O_i \sum D_j$如果不等行列约束会互相拉扯永远无法同时满足。解决方法是先检查两个总和如果不等按比例调整 $D_j$ 或 $O_i$让总和一致。我一般会在代码开头加一行断言assert abs(O.sum() - D.sum()) 1e-6提前拦住这个问题。4.2 零单元格导致未来OD表出现大量零现象最终OD矩阵里某些小区对之间始终是零即使未来这些小区之间应该有出行。原因就是现状OD矩阵里的零单元格在迭代中无法被“激活”。解决方法是在预处理阶段给零单元格一个种子值或者在迭代完成后用重力模型对零单元格做补充。种子值不宜太大0.1 到 1 之间即可太大会影响整体收敛。4.3 收敛阈值设太小导致迭代次数暴增现象EPS 设成 0.001结果跑了 500 轮还没停。原因是增长系数法的收敛速度是线性的阈值每缩小一个数量级迭代次数大约增加 30% 到 50%。实际项目中EPS0.01 已经足够对应行列和误差在 1% 以内完全满足规划精度要求。如果非要更小建议先用大阈值跑再用结果作为初始值跑小阈值可以省不少时间。4.4 单位不一致导致结果量纲错误现象最终OD表的数值和现状OD表差了几个数量级。原因通常是发生量或吸引量的单位和现状OD不一致。比如现状OD是车次/日未来控制量是人次/日迭代结果就是人次/日的分布但后续步骤可能按车次处理导致全线错误。解决方法是在项目开始时就统一单位并在代码里加注释标明单位。我一般会在变量名里带上单位比如O_person、T_vehicle虽然啰嗦但能避免大错。4.5 弗兰特法的L因子不更新或更新错误现象弗兰特法跑出来的结果和平均系数法几乎一样没有体现出方法优势。原因是 $L$ 因子没有在每轮迭代中更新或者更新公式写错了。$L_i$ 的更新依赖当前轮的 $T_{ij}$、$F_{D_j}$ 和上一轮的 $L_j$三者缺一不可。常见错误是用了初始的 $L_j1$ 而不是更新后的值。解决方法是严格按照公式实现并在每轮迭代后打印 $L$ 数组观察它是否在变化。如果 $L$ 一直全是 1说明更新逻辑没生效。5. 从跑通到用好一个验证收敛结果的小技巧跑完四种方法后怎么判断结果靠不靠谱我常用的技巧是做一次“反向验证”把最终OD矩阵的行和列分别除以未来控制量看增长系数是否都在 1±0.01 内。这其实就是收敛判断的逆运算但单独做一遍能发现代码里可能存在的隐藏 bug比如收敛判断写错了但迭代恰好停了。更进一步可以做一个敏感性测试把未来发生量 $O_i$ 整体上调 5%重新跑一遍观察最终OD矩阵的变化是否平滑。如果某些小区对的结果跳变超过 20%说明这些小区对之间的出行分布对控制量过于敏感需要在报告中特别说明。这个测试不需要额外代码改一下O数组重新运行即可。还有一个实用习惯把四种方法的最终OD矩阵存成 CSV用 pandas 做两两差值对比。差值超过 10% 的单元格单独列出来回头检查这些小区对的现状OD和未来控制量是否合理。这个步骤在正式项目里帮我抓过好几次数据错误比如某个小区的吸引量被误填成了发生量。最后一个技巧是关于迭代顺序的。佛尼斯法先调行还是先调列结果会有细微差异。我一般会跑两次一次先调行一次先调列如果两次结果差异超过 5%说明数据本身的结构性矛盾较大需要回头检查出行生成。如果差异很小就取先调行的结果因为发生量通常比吸引量更可靠。这些习惯都是踩坑踩出来的。刚开始做交通规划时我只顾着把代码跑通结果交出去的OD表被导师一眼看出列和对不上返工了两天。后来养成了每跑完一次就做反向验证的习惯再也没有在收敛性上翻过车。希望帮到你。本文还有配套的精品资源点击获取