交通分布预测四方法详解:弗兰特法、底特律法、平均系数法与佛尼斯法
简介这份资源面向交通规划课程学习者与课后习题编程实践者聚焦交通量生成与预测这一核心环节提供弗兰特法、底特律法、平均系数法与佛尼斯法四种常用方法的MATLAB实现。压缩包共4个文件均为m脚本整体约2KB分别对应各方法的算法流程弗兰特法基于历史数据做线性增长估算底特律法引入道路饱和度调整增长率平均系数法统计历史平均增长系数佛尼斯法则通过路网建模与迭代分配预测路段流量。目前已有928人学习下载说明其在课程作业与自学场景中具有一定参考价值。读者可直接运行脚本、调整参数并对比四种方法的预测结果理解不同模型的适用条件与差异也可在此基础上修改代码探索多因素影响下的交通量变化为交通规划课程习题与小型预测实验提供可复用的代码基础。1. 从一道课后题说起四类交通分布模型到底在算什么如果你手头有一份交通规划课后习题题目给了几个小区的发生吸引量、阻抗矩阵然后让你分别用弗兰特法、底特律法、平均系数法和佛尼斯法算未来年的出行分布——这大概率就是你要面对的场景。这四种方法本质上都在解决同一个问题已知各交通小区的出行产生量和吸引量如何推算小区之间的出行交换量。区别在于它们对增长因子的处理逻辑不同收敛速度、适用条件和计算复杂度也各不相同。弗兰特法收敛慢但稳定底特律法用吸引量做归一化平均系数法把两者取平均佛尼斯法则通过迭代调整吸引量来逼近目标。课后习题通常要求手算或编程实现而编程实现的关键在于理解每种方法的迭代公式和收敛判据。这份资源适合正在做交通规划课程设计的学生也适合需要快速复现这四种方法做方案比选的从业者。2. 四种方法的数学骨架增长因子怎么算、迭代怎么收敛2.1 弗兰特法最直观的迭代起点弗兰特法的核心思想是用现状OD矩阵乘以一个增长因子然后反复调整行和列直到满足未来年的发生量和吸引量约束。具体来说先计算各小区的发生增长系数和吸引增长系数然后对每个OD对做一次双约束修正。假设现状OD矩阵为 ( T_{ij} )未来发生量为 ( O_i )未来吸引量为 ( D_j )。弗兰特法的迭代步骤是计算初始增长因子 ( F_i^O O_i / \sum_j T_{ij} )( F_j^D D_j / \sum_i T_{ij} )更新OD矩阵( T_{ij}^{(k1)} T_{ij}^{(k)} \cdot F_i^O \cdot F_j^D )重新计算行列和检查是否满足收敛条件若不满足重复步骤1-3这个方法的优点是公式简单容易编程实现。缺点是收敛速度较慢尤其是当发生量和吸引量不平衡时可能需要几十次迭代。课后习题里如果要求手算一般会限制迭代次数比如3-5次就停。import numpy as np def furness(T, O_target, D_target, max_iter100, tol1e-4): 弗兰特法Furness method实现 T: 现状OD矩阵shape (n, n) O_target: 未来发生量shape (n,) D_target: 未来吸引量shape (n,) max_iter: 最大迭代次数 tol: 收敛容差 T T.astype(float) n T.shape[0] for k in range(max_iter): # 行约束调整发生量 O_current T.sum(axis1) F_O O_target / O_current T T * F_O[:, np.newaxis] # 列约束调整吸引量 D_current T.sum(axis0) F_D D_target / D_current T T * F_D[np.newaxis, :] # 检查收敛 O_err np.abs(T.sum(axis1) - O_target).max() D_err np.abs(T.sum(axis0) - D_target).max() if O_err tol and D_err tol: print(f弗兰特法在第 {k1} 次迭代收敛) break return T这段代码里F_O和F_D分别是行和列的增长因子。注意每次迭代先调整行再调整列顺序不影响最终结果但会影响中间过程的收敛速度。tol一般取 1e-4 到 1e-6课后习题里如果只要求保留两位小数取 1e-2 就够了。2.2 底特律法用吸引量做归一化底特律法和弗兰特法很像区别在于它每次迭代时增长因子只作用于行方向然后用列和来归一化。具体公式是( T_{ij}^{(k1)} T_{ij}^{(k)} \cdot F_i^O \cdot \frac{D_j}{\sum_i T_{ij}^{(k)} \cdot F_i^O} )这个方法的收敛速度比弗兰特法快一些因为它每次迭代都强制列和等于目标吸引量。但缺点是如果吸引量分布不合理可能会导致某些OD对出现负值或零值。课后习题里经常把底特律法和弗兰特法放在一起对比让你观察迭代次数的差异。def detroit(T, O_target, D_target, max_iter100, tol1e-4): 底特律法Detroit method实现 T T.astype(float) n T.shape[0] for k in range(max_iter): # 行增长因子 O_current T.sum(axis1) F_O O_target / O_current T T * F_O[:, np.newaxis] # 列归一化 D_current T.sum(axis0) T T * (D_target / D_current)[np.newaxis, :] # 检查收敛 O_err np.abs(T.sum(axis1) - O_target).max() D_err np.abs(T.sum(axis0) - D_target).max() if O_err tol and D_err tol: print(f底特律法在第 {k1} 次迭代收敛) break return T底特律法的代码和弗兰特法几乎一样区别在于列归一化时直接乘的是D_target / D_current而不是先算列增长因子再乘。这个细微差别导致底特律法在每次迭代后列和严格等于目标吸引量而行和需要下一次迭代才修正。2.3 平均系数法取两者之长平均系数法的思路很简单把弗兰特法和底特律法的增长因子取平均。具体来说先分别计算行增长因子和列增长因子然后取算术平均或几何平均。课后习题里通常要求用算术平均。( F_{ij} \frac{1}{2} \left( F_i^O F_j^D \right) )然后更新OD矩阵( T_{ij}^{(k1)} T_{ij}^{(k)} \cdot F_{ij} )这个方法的收敛速度介于弗兰特法和底特律法之间但计算量稍大因为每个OD对都要算一个独立的增长因子。实际项目中如果发生量和吸引量比较均衡平均系数法的表现往往最稳定。def average_factor(T, O_target, D_target, max_iter100, tol1e-4): 平均系数法实现 T T.astype(float) n T.shape[0] for k in range(max_iter): O_current T.sum(axis1) D_current T.sum(axis0) F_O O_target / O_current F_D D_target / D_current # 算术平均 F_avg 0.5 * (F_O[:, np.newaxis] F_D[np.newaxis, :]) T T * F_avg O_err np.abs(T.sum(axis1) - O_target).max() D_err np.abs(T.sum(axis0) - D_target).max() if O_err tol and D_err tol: print(f平均系数法在第 {k1} 次迭代收敛) break return T注意F_avg是一个 n×n 的矩阵每个元素是行增长因子和列增长因子的平均值。这种逐对修正的方式让平均系数法在初期收敛很快但后期可能会出现震荡需要适当降低收敛容差。2.4 佛尼斯法调整吸引量的迭代策略佛尼斯法和前面三种不太一样。它不直接调整OD矩阵而是通过迭代调整吸引量来间接满足约束。具体步骤是初始化吸引量 ( D_j^{(0)} D_j )对每个小区计算 ( T_{ij} O_i \cdot \frac{D_j^{(k)} \cdot f(c_{ij})}{\sum_j D_j^{(k)} \cdot f(c_{ij})} )计算新的吸引量 ( D_j^{(k1)} D_j^{(k)} \cdot \frac{D_j}{\sum_i T_{ij}} )重复直到收敛其中 ( f(c_{ij}) ) 是阻抗函数的倒数常见的是 ( f(c_{ij}) e^{-\beta c_{ij}} ) 或 ( f(c_{ij}) c_{ij}^{-\beta} )。佛尼斯法的优势在于它天然满足发生量约束只需要调整吸引量适合发生量已知但吸引量需要反推的场景。def furness_attraction(O_target, D_target, cost, beta0.1, max_iter100, tol1e-4): 佛尼斯法Furness method for attraction adjustment O_target: 发生量 D_target: 目标吸引量 cost: 阻抗矩阵 beta: 阻抗函数参数 n len(O_target) D D_target.copy().astype(float) f np.exp(-beta * cost) # 阻抗函数 for k in range(max_iter): # 计算OD矩阵 T np.zeros((n, n)) for i in range(n): denom np.sum(D * f[i, :]) for j in range(n): T[i, j] O_target[i] * D[j] * f[i, j] / denom # 更新吸引量 D_new D * (D_target / T.sum(axis0)) # 检查收敛 if np.abs(D_new - D).max() tol: print(f佛尼斯法在第 {k1} 次迭代收敛) break D D_new return T这段代码里beta是阻抗函数的参数控制出行距离的衰减速度。beta越大短距离出行占比越高。课后习题里通常会给定beta值或者让你用最大似然估计反推。注意佛尼斯法的收敛判据是吸引量的变化量而不是OD矩阵的行列和误差。3. 编程实现从课后习题到可复现脚本3.1 数据准备与矩阵对齐课后习题通常会给一个表格列出各小区的现状OD量和未来发生吸引量。第一步是把这些数据转成NumPy数组。常见做法是手动输入但如果题目给了Excel或CSV可以用pandas读取。import pandas as pd # 假设CSV文件有三列origin, destination, trips df pd.read_csv(od_matrix.csv) n df[origin].nunique() T df.pivot(indexorigin, columnsdestination, valuestrips).values # 未来发生量和吸引量 O_target np.array([1200, 1500, 1800, 2000]) D_target np.array([1300, 1400, 1700, 2100])注意T的行列顺序要和O_target、D_target对应。如果题目里的小区编号不是从0开始需要先做映射。我一般会写一个zone_map字典把原始编号转成0-based索引。3.2 收敛判据与迭代次数控制四种方法的收敛判据可以统一用最大绝对误差def check_convergence(T, O_target, D_target, tol1e-4): O_err np.abs(T.sum(axis1) - O_target).max() D_err np.abs(T.sum(axis0) - D_target).max() return O_err tol and D_err tol但佛尼斯法的判据不同它看的是吸引量的变化量。课后习题里如果要求手算一般会指定迭代次数比如“迭代3次后停止”。编程实现时建议同时输出每次迭代的误差方便观察收敛趋势。def run_all_methods(T, O_target, D_target, max_iter50): results {} results[furness] furness(T.copy(), O_target, D_target, max_iter) results[detroit] detroit(T.copy(), O_target, D_target, max_iter) results[average] average_factor(T.copy(), O_target, D_target, max_iter) # 佛尼斯法需要阻抗矩阵 cost np.ones_like(T) # 如果没有阻抗数据用全1矩阵 results[furness_attraction] furness_attraction(O_target, D_target, cost) return results这段代码把四种方法封装成一个函数方便对比结果。注意每次调用都要传T.copy()否则会修改原始矩阵。3.3 结果对比与可视化课后习题通常要求列出最终OD矩阵并计算各方法的迭代次数。可以用pandas整理成表格def compare_results(results, O_target, D_target): rows [] for name, T in results.items(): O_err np.abs(T.sum(axis1) - O_target).max() D_err np.abs(T.sum(axis0) - D_target).max() rows.append({ method: name, max_O_error: round(O_err, 4), max_D_error: round(D_err, 4), total_trips: round(T.sum(), 2) }) return pd.DataFrame(rows)如果题目要求画图可以用matplotlib画一个热力图直观对比四种方法的OD分布差异。但课后习题一般不需要手算的话列出矩阵就行。4. 避坑与排查课后习题里最容易翻车的五个点4.1 现象迭代不收敛误差越来越大原因发生量和吸引量总和不相等。弗兰特法和底特律法都要求 ( \sum O_i \sum D_j )如果不相等迭代会震荡。解决先检查题目给的数据如果总和不一致需要按比例调整。常见做法是把吸引量按发生量总和缩放。4.2 现象OD矩阵出现负值原因增长因子计算时分母为零或者阻抗函数参数beta过大导致指数下溢。解决检查是否有小区的发生量或吸引量为零。如果有需要单独处理比如把该小区的OD对设为零不参与迭代。4.3 现象佛尼斯法收敛到局部最优原因初始吸引量设置不合理或者阻抗函数形式选错。解决佛尼斯法对初始值敏感建议用目标吸引量作为初始值。如果题目给了阻抗矩阵先确认beta的符号f(c) exp(-beta * c)里beta应为正数。4.4 现象平均系数法后期震荡原因行增长因子和列增长因子的平均值可能超过1或小于0导致OD矩阵元素忽大忽小。解决改用几何平均 ( F_{ij} \sqrt{F_i^O \cdot F_j^D} )或者降低收敛容差比如从1e-4放宽到1e-2。4.5 现象手算结果和编程结果对不上原因手算时四舍五入的精度损失或者迭代顺序不同。解决手算时保留至少4位小数编程时用float64。如果题目要求保留两位小数最后一步再舍入中间过程不要舍入。5. 进阶技巧用佛尼斯法反推阻抗参数佛尼斯法的一个常见变体是用它来标定阻抗函数的参数beta。课后习题里如果给了现状OD矩阵和阻抗矩阵让你反推beta可以用最大似然估计或最小二乘法。具体做法是先给beta一个初始值用佛尼斯法算出模拟OD矩阵然后计算模拟值和观测值的误差用梯度下降或网格搜索调整beta。from scipy.optimize import minimize_scalar def calibrate_beta(T_obs, O_target, D_target, cost): 用佛尼斯法标定beta T_obs: 观测OD矩阵 def loss(beta): T_sim furness_attraction(O_target, D_target, cost, beta) return np.sum((T_sim - T_obs) ** 2) result minimize_scalar(loss, bounds(0.01, 1.0), methodbounded) return result.x # 示例 beta_opt calibrate_beta(T, O_target, D_target, cost) print(f最优beta: {beta_opt:.4f})这段代码用scipy.optimize.minimize_scalar做一维搜索bounds根据实际阻抗范围调整。如果题目没有要求标定可以跳过这一步。但如果你在做课程设计这个技巧能让你的报告多一个亮点。另一个进阶用法是把四种方法的结果做加权平均得到组合预测。权重可以用历史数据的拟合优度确定或者简单取等权。我一般会先跑一遍四种方法看哪个的误差最小然后以它为主其他为辅。从那以后我每次做交通分布预测都会先把发生量和吸引量的总和核对一遍再检查阻抗矩阵的对称性和量纲。这个习惯帮我省了不少返工时间。希望帮到你。本文还有配套的精品资源点击获取