☰
轨道交通仿真系统实战:列车运行仿真与乘客流耦合的代码实现与避坑指南
2026/10/22 10:40:11 网站建设 项目流程

简介:这份资源是面向交通工程、城市规划与仿真开发方向学习者的轨道交通仿真系统代码包,聚焦列车运行过程与乘客流动的计算机模拟,可用于理解列车启动、加速、匀速、减速、停车等环节,以及线路条件、信号系统和牵引供电等因素的整合方式。压缩包共20个文件,约107KB,以m脚本和mat数据文件为主,辅以png示意图、fig图形界面文件与txt说明,分别承担算法实现、参数存储、结果可视化与数据配置等用途,便于按模块阅读与调试。资源标签涉及列车运行仿真程序、轨道交通乘客与duringpw7高峰时段场景,代码中涵盖距离计算、速度求解、站点状态显示、位置更新与随机客流生成等逻辑,可帮助读者掌握仿真模型的数据结构、控制流程与公式定义,并据此调整参数、排查问题或扩展新功能。目前已有419人学习下载,适合作为课程设计、毕业设计或运营策略评估的参考素材。

1. 轨道交通仿真系统:从一份列车运行仿真代码里能挖出什么

很多人第一次接触轨道交通仿真系统,是被一个压缩包砸脸的——里面躺着列车运行仿真程序、乘客流模块、线路数据,还有一堆看不懂的参数文件。你打开主程序,发现它既不是纯物理引擎,也不是纯调度算法,而是把列车牵引计算、站间运行曲线、乘客上下车时间、发车间隔全揉在一起。这东西能解决什么问题?简单说,它让你在电脑里跑一条虚拟线路,看列车怎么加速、怎么巡航、怎么进站停车,以及站台上的人怎么挤上去、怎么散开。适合谁?做交通规划验证的、做信号系统测试的、做运营方案比选的,甚至做教学演示的,都能用。但前提是,你得先搞明白它的输入输出到底长什么样,别一上来就改代码。

2. 列车运行仿真程序的核心模块拆解:牵引、限速与站间曲线

2.1 牵引计算不是玄学,是分段积分

列车运行仿真最底层的东西是牵引计算。你给一列车一个牵引力曲线和阻力曲线,它就能算出加速度,进而积分出速度和位移。常见做法是把它拆成三个区段:牵引区、惰行区、制动区。牵引区里,牵引力大于阻力,列车加速;惰行区里,牵引力为零,只有阻力和坡度分力在起作用;制动区里,制动力叠加阻力,列车减速。每个区段的切换点,就是所谓的工况转换点。

我一般会先确认代码里有没有把基本阻力公式写对。戴维斯公式是最常用的:R = a + bv + cv^2,其中 a 是滚动阻力系数,b 是机械阻力系数,c 是空气阻力系数。这三个系数如果给错,整条速度曲线都会飘。比如某次我拿到一份仿真程序,跑出来的最高速度比设计值高了 12 km/h,查了半天发现是 c 系数少了一个零。这种坑,血泪经验就是:先拿单车、平直道、无风条件跑一遍,看加速时间和距离对不对得上手算值。

# 戴维斯基本阻力公式,单位:N def davis_resistance(v, a=1.5, b=0.008, c=0.00015): """ v: 列车速度,单位 m/s a: 滚动阻力系数,典型值 1.0~2.0 b: 机械阻力系数,典型值 0.005~0.012 c: 空气阻力系数,典型值 0.0001~0.0003 返回:基本阻力,单位 N """ return a + b * v + c * v * v # 单质点列车运动方程 def train_dynamics(v, F_traction, F_brake, mass, gradient_force): """ v: 当前速度 m/s F_traction: 牵引力 N F_brake: 制动力 N mass: 列车质量 kg gradient_force: 坡道附加阻力 N,上坡为正 返回:加速度 m/s^2 """ R = davis_resistance(v) net_force = F_traction - F_brake - R - gradient_force return net_force / mass

这段代码里,davis_resistance的三个系数需要根据车型查表,不能随便填。train_dynamics里把坡道力单独拿出来,是因为在仿真中坡道是随位置变化的,每步都要重新算。如果你偷懒把坡道力写成常数,长隧道或者大坡道区段的速度曲线就会明显失真。

2.2 限速曲线怎么叠到速度曲线上

线路限速不是一条平线,它是一段一段的阶梯。仿真程序里通常用一张表存:起点里程、终点里程、限速值。列车运行时要取当前里程对应的最小限速,然后跟当前速度比。如果超了,就得触发制动。这里有个容易翻车的地方:限速切换点附近,如果步长太大,列车可能“冲”过限速区才反应过来。我一般会把步长控制在 0.1 秒以内,或者在限速下降点前提前设置一个制动触发点。

# 限速表查询与超速判断 speed_limits = [ (0, 500, 80), # 0~500米,限速80 km/h (500, 1200, 60), # 500~1200米,限速60 km/h (1200, 2000, 100) # 1200~2000米,限速100 km/h ] def get_current_limit(position): """position: 当前里程,单位米;返回限速,单位 km/h""" for start, end, limit in speed_limits: if start <= position < end: return limit return 0 # 超出范围,视为停车 def check_overspeed(v_kmh, position, margin=2.0): """ v_kmh: 当前速度 km/h position: 当前里程 m margin: 允许的超速余量 km/h 返回:是否需要制动 """ limit = get_current_limit(position) return v_kmh > (limit + margin)

margin这个参数是给控制留余量的,一般设 2~5 km/h。设太小,制动频繁;设太大,容易真超速。get_current_limit里用<而不是<=,是为了避免边界重复。这些细节在代码里不起眼,但跑长交路时会影响旅行时间和能耗。

2.3 站间运行曲线的生成与验证

站间运行曲线是仿真输出里最直观的东西:横轴里程,纵轴速度。一条典型的曲线是“牵引-巡航-惰行-制动”四段。生成方式有两种:一种是按最大能力跑,即全牵引到限速,然后尽量惰行,最后最大制动;另一种是按定时跑,给定站间运行时间,反推工况切换点。代码里通常用前者做基准,后者做调整。

验证曲线对不对,我一般看三个指标:最高速度是否接近限速、惰行段是否足够长、制动点是否在站台前合适位置。如果最高速度远低于限速,可能是牵引力给小了;如果惰行段几乎没有,可能是阻力设大了;如果制动点太早,旅行时间会变长。这些判断不需要高深理论,跑几遍就能摸出规律。

3. 乘客流与列车运行的耦合:站停时间怎么算才不崩

3.1 上下车人数与站停时间的经验模型

乘客流模块的核心输出是站停时间。站停时间 = 开门时间 + 下车时间 + 上车时间 + 关门时间。下车时间取决于下车人数和车门通行能力,上车时间取决于上车人数、车门通行能力,以及站台是否拥挤。常见做法是用线性模型:T = N / (C * n_doors) + t_door,其中 N 是人数,C 是单人通过率(人/秒/门),n_doors 是车门数,t_door 是开关门固定时间。

但线性模型在人多的时候会偏乐观。实际中,上车和下车会互相干扰,站台拥挤时通过率会下降。我一般会加一个拥挤系数:当站台人数超过阈值时,C 乘以一个 0.6~0.8 的折减。这个系数没有统一标准,得根据线路实测数据调。

def dwell_time(alight, board, doors=6, capacity=1.2, door_time=30, crowd_factor=1.0): """ alight: 下车人数 board: 上车人数 doors: 车门数 capacity: 单门通过率,人/秒 door_time: 开关门总时间,秒 crowd_factor: 拥挤折减系数,0.6~1.0 返回:站停时间,秒 """ effective_capacity = capacity * crowd_factor t_alight = alight / (doors * effective_capacity) t_board = board / (doors * effective_capacity) # 上下车可部分重叠,取较大者加一个重叠系数 t_exchange = max(t_alight, t_board) + 0.3 * min(t_alight, t_board) return door_time + t_exchange

crowd_factor是关键参数。平峰期可以取 1.0,高峰期取 0.7 左右。t_exchange里那个 0.3 是重叠系数,意思是上下车同时进行时,总时间不是简单相加。这个值也是经验值,不同车型、不同站台布局会有差异。

3.2 乘客到达与列车容量的匹配

乘客流模块还需要模拟乘客到达。常见做法是用泊松过程:单位时间到达人数服从泊松分布,均值等于该站该时段的客流量。列车到站后,站台等待人数加上车人数,但不能超过列车剩余容量。如果超了,就得留乘,留乘人数累积到下一班。

这里有个容易忽略的点:留乘会改变下一班的站停时间,进而影响后续列车的到站时间,形成连锁反应。仿真程序里如果没处理这个反馈,高峰期跑出来的时刻表会偏乐观。我一般会在代码里加一个站台人数队列,每列车到站时先更新队列,再算上下车。

import random class Station: def __init__(self, name, arrival_rate): self.name = name self.arrival_rate = arrival_rate # 人/秒 self.waiting = 0 def generate_passengers(self, dt): """按泊松过程生成到达乘客""" n = 0 # 小步长下用泊松分布近似 if self.arrival_rate * dt < 0.01: if random.random() < self.arrival_rate * dt: n = 1 else: n = int(random.gauss(self.arrival_rate * dt, (self.arrival_rate * dt) ** 0.5)) n = max(0, n) self.waiting += n return n def board_train(self, capacity_left): """返回实际上车人数和留乘人数""" actual_board = min(self.waiting, capacity_left) self.waiting -= actual_board return actual_board, self.waiting

generate_passengers里用高斯分布近似泊松,在到达率不高时够用。board_train返回留乘人数,这个值要传给下一班列车。如果代码里没有这个返回值,说明留乘逻辑没做全。

3.3 列车容量与超载判断

列车容量分定员和超员。定员是座位加站立标准,超员是极限值。仿真里一般用定员做舒适度判断,用超员做安全判断。如果某站上车后人数超过超员,就得强制留乘。这个逻辑在代码里通常是一个 if 判断,但参数得设对。比如 B 型车 6 节编组,定员大概 1400 人,超员能到 1800 人左右。这些数值得查车型手册,不能拍脑袋。

4. 仿真步长、事件调度与性能:跑得快还要跑得稳

4.1 固定步长与事件驱动的选择

列车运行仿真有两种主流调度方式:固定步长和事件驱动。固定步长就是每 0.1 秒或 0.01 秒算一次,简单直接,但计算量大。事件驱动是只在工况切换、到站、发车这些事件点计算,效率高,但实现复杂。我一般建议:如果线路不长、列车不多,固定步长够用;如果要做全线网、多车追踪,事件驱动更合适。

固定步长的坑在于步长选择。步长太大,速度曲线会锯齿;步长太小,跑得慢。我一般先用 0.1 秒跑一遍,看曲线是否平滑,如果制动段有明显台阶,就降到 0.05 秒。事件驱动则要注意事件排序,同一时刻多个事件(比如到站和发车)要有优先级。

# 固定步长仿真主循环 def simulate_fixed_step(train, line, dt=0.1, max_time=3600): """ train: 列车对象 line: 线路对象,含限速表、坡道表、车站表 dt: 步长,秒 max_time: 最大仿真时间,秒 """ t = 0 log = [] while t < max_time: position = train.position v = train.speed # 获取当前限速和坡道 limit = line.get_limit(position) gradient = line.get_gradient(position) # 计算牵引/制动力 F_traction = train.get_traction(v, limit) F_brake = train.get_brake(v, limit) # 更新加速度和速度 a = train_dynamics(v, F_traction, F_brake, train.mass, gradient) v_new = v + a * dt # 限速保护 if v_new * 3.6 > limit: v_new = limit / 3.6 train.speed = v_new train.position += v_new * dt # 到站判断 if line.is_station(train.position): dwell = line.handle_station(train) t += dwell log.append((t, train.position, train.speed)) t += dt return log

dt是核心参数,0.1 秒适合大多数场景。get_traction和get_brake里要根据当前速度和限速决定输出,不能一直给最大牵引。is_station判断到站后,handle_station会算站停时间并更新乘客。这个循环里没有处理多车追踪,如果要加,得在line里维护一个列车列表,每步检查前车位置。

4.2 多车追踪与最小间隔

多车追踪的核心是保证后车不撞前车。常见做法是计算后车到前车车尾的距离,如果小于最小安全间隔,就触发制动。最小安全间隔包括:前车长度、后车制动距离、安全余量。制动距离又跟速度有关,所以这是一个动态值。

代码里一般用一个get_safe_distance(v, v_lead)函数,返回当前速度下的最小安全距离。如果实际距离小于这个值,后车就按最大制动减速。这个逻辑在固定步长里每步都要算,计算量不小。优化方法是只在距离接近阈值时才精细计算,平时用粗略估计。

def safe_distance(v, v_lead, decel=1.0, reaction=1.5, margin=50): """ v: 后车速度 m/s v_lead: 前车速度 m/s decel: 后车制动减速度 m/s^2 reaction: 反应时间 s margin: 安全余量 m 返回:最小安全距离 m """ # 后车制动距离 d_brake = v * reaction + v * v / (2 * decel) # 前车在反应时间内的位移 d_lead = v_lead * reaction return d_brake - d_lead + margin

decel一般取 1.0 m/s² 左右,这是常用制动减速度。reaction取 1.5 秒是保守值。margin至少 50 米,如果线路有坡道,还得加坡度修正。这个函数返回的距离如果大于实际距离,后车就必须减速。

4.3 性能优化:向量化与缓存

如果仿真线路长、列车多,纯 Python 循环会跑得很慢。我一般会做两件事:一是把限速表和坡道表预处理成 numpy 数组,用插值代替循环查找;二是把每步重复计算的东西缓存起来,比如戴维斯阻力系数只跟速度有关,可以预先算一张表。

import numpy as np # 预计算阻力表 v_table = np.linspace(0, 50, 501) # 0~50 m/s R_table = davis_resistance(v_table) def get_resistance_fast(v): """用插值快速获取阻力""" return np.interp(v, v_table, R_table)

np.interp比每次调函数快很多,精度也够。如果步长是 0.1 秒,速度变化不大,插值误差可以忽略。这个技巧在跑长交路时能省不少时间。

5. 避坑与排查:仿真跑飞了先看这五个地方

5.1 速度曲线出现负值或震荡

现象:速度曲线在制动段出现负速度,或者来回震荡。原因:步长太大,制动减速度给得过猛,一步就从正速度跳到负速度。解决:减小步长到 0.05 秒,或者在制动逻辑里加一个速度下限判断,当速度小于 0.5 m/s 时直接置零。

5.2 列车冲过站台才停车

现象:列车到站位置超过站台中心,甚至冲过站台。原因:制动触发点太晚,或者制动减速度设小了。解决:在到站前一个区段就检查剩余距离,如果小于制动距离,提前触发制动。制动减速度按常用制动取值,不要用紧急制动。

5.3 站停时间越跑越长

现象:高峰期仿真跑几站后,站停时间累积到几分钟。原因:留乘逻辑没做,站台人数无限累积。解决:检查board_train是否返回留乘人数,并且下一班列车到站时是否把留乘人数加进等待队列。如果没加,站台人数会只增不减。

5.4 多车追踪时后车突然消失

现象:后车在追踪过程中位置突变,或者直接跳出线路范围。原因:安全距离计算里前车速度用了后车速度,或者距离判断时前后车搞反了。解决:在safe_distance里明确参数顺序,前车速度是v_lead,后车速度是v。调试时打印前后车位置和距离,看是否合理。

5.5 仿真结果与实测数据偏差大

现象:旅行时间、能耗跟实际线路对不上。原因:基本阻力系数、牵引力曲线、坡道数据有一个或多个不准。解决:先拿平直道、单车、无乘客的基准场景跑,跟手算值对比。如果基准都对不上,别急着调乘客模块,先把牵引计算查清楚。

6. 进阶技巧:用参数扫描找最优发车间隔

仿真跑通之后,最有价值的用法是做参数扫描。比如你想知道某条线路在早高峰的最优发车间隔是多少,可以写一个外层循环,让发车间隔从 120 秒到 300 秒变化,每次跑一遍仿真,记录总旅行时间、最大站台等待人数、列车平均满载率。然后画一张图,找那个总成本最低的点。

def scan_headway(line, train_proto, headways, sim_time=7200): """ line: 线路对象 train_proto: 列车原型,每次深拷贝 headways: 发车间隔列表,秒 sim_time: 仿真时长,秒 返回:每个发车间隔对应的指标 """ results = [] for hw in headways: trains = [] t = 0 while t < sim_time: new_train = copy.deepcopy(train_proto) new_train.depart_time = t trains.append(new_train) t += hw # 跑仿真,记录指标 log = simulate_multi_train(trains, line, sim_time) avg_wait = compute_avg_wait(log) max_wait = compute_max_wait(log) avg_load = compute_avg_load(log) results.append((hw, avg_wait, max_wait, avg_load)) return results

headways一般从 120 秒到 300 秒,步长 10 秒。sim_time至少覆盖整个早高峰,7200 秒是两小时。compute_avg_wait算乘客平均等待时间,compute_max_wait算最大等待时间,compute_avg_load算列车平均满载率。这三个指标一起看,才能判断发车间隔是否合理。如果只看平均等待,可能会选一个发车很密的方案,但满载率太低,运营成本高。

我自己的习惯是:先跑基准场景,确认单车曲线对得上;再加乘客,看站停时间是否合理;最后加多车,看追踪是否稳定。每一步都留一份日志,出问题就回退到上一步。这个流程看起来笨,但能省下大量排查时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询