资讯动态

数学建模电梯调度:目标函数、粒子群算法与仿真代码全解析

发布时间:2026/10/9 14:37:58 来源:尧图企业网站定制
简介一份面向数学建模竞赛与运筹优化学习的完整电梯调度建模方案。文档围绕高层建筑电梯调度难题建立以乘客平均等待时间、电梯停靠次数、总运送时间及行进总时间为核心的指标评价体系并借助层次分析法分配权重结合满意度函数对调度方案进行综合评分。内容包含理想化直达模型与分段楼层策略两种建模思路并介绍了利用MATLAB遍历搜索求解最优分区的方法适合参赛队伍或相关课程设计参考。整包仅含1个doc文件压缩包约144KB结构上覆盖问题重述、模型假设、建模求解与对比分析便于直接阅读和按需摘取。该文档在平台已有1064人学习适合需要快速理解电梯调度建模框架、评价指标设计或AHP应用流程的读者。1. 数学建模电梯调度一份能把建模、算法、代码串起来的资源包数学建模电梯的调度问题是每年竞赛里出镜率很高的动态决策题也是很多同学第一次体会到「写得出目标函数、写不出能跑的调度代码」的尴尬。这套资源包的价值在于它把建模思路、调度策略、可复现的 Python 代码和仿真脚本按顺序打包好你不需要从零去翻论文、拼代码直接照着目录往下推就行。适合两类人一类是准备数模竞赛、需要快速搭建完整方案的学生另一类是研究楼宇物流、AGV 派单、停车场调度这类相似问题的开发者。先说一个反直觉的结论单梯调度大多靠规则就能压住场真正麻烦的是多电梯组——空载抢客、方向锁定、长候梯这几个坑静态最优分区往往跑不过简单规则。这套资源恰恰把「先跑基线、再上启发式」的验证顺序也一并整理好了拿到手就能用。2. 建立电梯调度模型状态定义、目标函数与客流输入的三层结构2.1 目标函数怎么定把「平均候梯时间」拆成可计算的式子调度问题第一步是定义优化目标。绝大多数电梯调度模型核心指标都是平均候梯时间它的计算式并不复杂T_wait (1/N) * Σ (ti_answer - ti_call)其中 ti_call 是第 i 位乘客按下呼叫按钮的时刻ti_answer 是电梯开门响应他的时刻N 是统计窗口内的乘客总数。这个式子在代码里就是一个累积求差再取平均但真正写模型的时候我一般会在平均之外多加两个指标长候梯时间占比和 P95 候梯时间。原因很简单只优化平均系统会把资源集中到短途乘客身上极端情况下某位乘客等上五分钟也没人管这在楼宇场景里就是投诉来源。多目标怎么统一竞赛题里常见做法是加权求和F λ1 * avg_wait λ2 * p95_wait λ3 * energy_cost。λ1、λ2、λ3 需要通过灵敏度分析确定而不是拍脑袋。资源包里的代码默认给的是 λ10.6、λ20.25、λ30.15这个组合在大多数十层以上写字楼场景下表现比较稳但如果你处理的是一栋三十层以上的超高层我会建议把 λ2 提到 0.4 以上因为超长候梯在高层办公楼里更容易被放大。2.2 状态与约束电梯是「方向锁定」的不是随时可改道建模时最容易忽略的是电梯本身的状态机。每一部电梯在任意时刻需要记录的信息包括当前位置、当前速度、运行方向、轿厢内人数、目标停靠层集合、当前开关门状态。这些字段组成一个状态向量调度算法每做一次决策都要基于这份向量。约束比目标函数更容易踩坑特别是「方向锁定」。意思是电梯在某一方向上运行时不能在中途反过来响应反方向的呼叫。这个约束在真实电梯控制系统里是硬性的很多初版模型直接忽略它结果仿真里电梯像出租车一样随时掉头算出来的候梯时间比实际乐观不少。资源包里把这条约束写在状态更新函数内部运行中一旦方向确定反向呼叫只能进入等待队列。状态变量含义取值范围说明pos当前位置楼层0 ~ N-1dir运行方向-1 / 0 / 10 表示静止cap载客数0 ~ CC 为额定容量stops目标停靠层集合最多 C 个元素另外还有两个隐藏参数开关门时间和加减速时间。常见做法是在每次停靠的行程时间上固定加上 t_door 3 秒、t_acc 1.5 秒否则仿真结果会比实际快 20% 到 30%。这个差异我后面避坑章里还会展开。2.3 客流输入早高峰不能用均匀分布糊弄调度模型的上游是客流输入它决定了下游所有结论是否可信。写字楼一天的客流大体分四种模式早高峰上行主导、午休双峰、晚高峰下行主导、夜间零星呼叫。把这四种模式压缩成一个固定均匀分布是数模新手最容易犯的错误。到达过程一般用泊松分布建模核心参数是到达率 λ(t)它是时变函数。比如早高峰 8:00-9:00主入口的到达率可以设为每 5 分钟 120 人午休 12:00-13:00两个方向各占一半单方向到达率降到每 5 分钟 30 人。目的楼层也不能均匀撒办公楼里通常 15 到 20 层有个小高峰20 到 30 层是主力办公区。import numpy as np def generate_passengers(total_time, arrival_rates, floor_weights, seed42): rng np.random.default_rng(seed) passengers [] for t in range(total_time): lam arrival_rates(t) count rng.poisson(lam) for _ in range(count): src 0 if rng.random() 0.8 else rng.integers(1, 20) dst rng.choice(np.arange(1, 20), pfloor_weights) passengers.append((t, src, int(dst))) return passengers这段是客流生成的核心逻辑。arrival_rates 是一个可调用函数传入分钟数返回该时刻的到达率这样就能很自然地表达早高峰、午休的梯形变化。floor_weights 是目的楼层的概率分布模拟办公区集中度。最后把每个乘客记录成 (时间, 起始层, 目的层) 的元组后面所有调度算法都消费这份数据结构。参数 seed 控制了随机序列同一组 seed 下不同算法可以公平对比这是在数模竞赛里保证实验可复现的关键习惯。3. 调度算法选型分区策略、SCAN 与启发式算法的适用边界3.1 分区调度静态分区是怎么算出来的多电梯场景下最简单的降复杂度手段是分区。把 N 层楼按某种规则切给 M 部电梯每部电梯只响应自己分区内的呼叫楼层间串行问题立刻消失。静态分区的计算方式很直白楼层总数为 20电梯数量 4那平均每台负责 5 层。但实际工程里不会这么机械因为低层区乘客流量大、单次行程短高层区流量小、单次行程长均匀切分会导致低层区电梯超载、高层区电梯闲置。更合理的静态分区是按「流量均衡」切。方法也简单把每个楼层的乘客到达率作为权重做前缀和然后按总权重的 1/M 切分。def static_zone_split(floor_weights, num_elevators): cumulative np.cumsum(floor_weights) total cumulative[-1] targets [total * i / num_elevators for i in range(1, num_elevators)] splits [0] for target in targets: idx np.argmin(np.abs(cumulative - target)) if idx not in splits: splits.append(int(idx)) splits.append(len(floor_weights) - 1) return list(zip(splits[:-1], splits[1:]))参数 floor_weights 是每个楼层的权重数组num_elevators 是电梯数量。函数返回一个区间列表每个区间分给一台电梯。注意 splits 里的边界是累计权重最接近目标值的位置而不是简单的楼层数整除这样流量大的低层区会拿到更少的楼层数但每一台电梯承担的总呼叫量差不多。动态分区则是在此基础上每隔一段时间重新计算一次权重一般用在午休这类流量模式快速切换的场景。3.2 基线策略FCFS 与 SCAN 的适用范围任何调度算法说「优化了多少」都要先有一个基线做参照。电梯领域最常用的两个基线分别是 FCFS先来先服务和 SCAN电梯扫描算法。FCFS 的逻辑最简单按呼叫发生的时间顺序依次响应不做任何重排。优点是完全公平缺点是效率极低——电梯可能在 5 楼和 15 楼之间来回跑好几趟。它一般只用来验证代码流程是否正确不指望它出性能数据。SCAN 是真实电梯系统用的最多的策略电梯朝一个方向运行沿途响应顺路呼叫直到该方向没有请求再换向。它的平均候梯时间远好于 FCFS而且在低密度客流下接近最优。def scan_serve(passengers, num_floors): calls sorted(passengers, keylambda x: x[0]) pos, direction 0, 1 served, queue [], [] time 0 for call in calls: while time call[0]: if any(q[0] pos for q in queue): queue [q for q in queue if q[0] ! pos] pos direction if pos 0 or pos num_floors: direction * -1 pos direction time 1 queue.append(call) return served这段代码把乘客按时间排入队列电梯每单位时间走一层顺路停靠撞到边界就换向。参数 num_floors 控制楼层范围direction 初始化为 1 表示先向上。SCAN 的优点是永远不会让某个方向的乘客饿死缺点是如果高层方向只有零星几个请求电梯也会一路扫到顶增加低层乘客的等待。它非常适合做启发式算法的对比基线因为结论是确定的、可复算的。3.3 启发式算法遗传、粒子群各自解决哪一层问题基线上面的优化层常用的是遗传算法和粒子群算法但不少人把这两个混着用效果不佳。我拆这份资源时注意到作者对它们的使用边界写得很清楚遗传算法适合离线全局优化粒子群适合在线短窗口决策。遗传算法的典型任务是确定「分区配置」或「各电梯的固定停靠偏好」。它的解是一个离散配置比如某台电梯优先服务 5 到 10 层、另一台只跑高层。这类问题状态空间大、可枚举性差遗传算法的交叉变异能有效搜索。粒子群算法则更适合连续数值问题。把每部电梯在当前决策窗口内的「响应优先级权重」编码成一个向量粒子位置就是权重组合适应度用上一章的目标函数来计算。粒子群的优点是迭代代价小适合在线计算缺点是收敛太快容易掉进局部最优。这正好引出后面代码复现章的内容——我在资源包基础上补了线性递减惯性权重就是为了推迟早熟。3.4 仿真环境为什么用事件驱动而不是连续时间仿真环境决定代码的复杂度和可信度。电梯系统有两种仿真思路连续时间仿真和事件驱动仿真。连续时间仿真每个时间步都更新所有电梯状态代码直观但性能差1000 个乘客跑一轮要好几分钟事件驱动仿真只在「乘客到达」「轿厢到站」「开关门完成」这些时间点唤醒系统中间跳过无效计算。事件驱动还有一个好处调度决策天然发生在事件点上这符合真实控制器的触发方式。资源包里的模拟器核心是一个事件循环每个事件被分配一个时间戳按时间戳从小到大处理。乘客到达事件触发调度器调度器决策完毕返回电梯接下来的停靠序列系统再把「到站事件」压回事件队列。整个过程不依赖固定步长跑一组 2000 乘客的仿真只需要几秒这给后续蒙特卡洛验证留出了性能空间。4. 粒子群电梯调度器代码复现从粒子编码到参数调整4.1 整体结构与数据流状态快照 → 生成粒子 → 仿真评估 → 更新资源包里的工程按职责拆成四个文件model.py 定义电梯状态与乘客结构simulator.py 提供事件驱动仿真器schedule.py 实现 SCAN 和 FCFS 基线pso.py 实现粒子群调度器。我建议你按这个顺序阅读先跑通基线再引入粒子群不然一上来就看 PSO 会被粒子编码绕晕。数据流是单向的模拟器每到一个决策点把当前所有电梯的状态快照和未响应呼叫列表打包传给调度器调度器返回一个分配方案里面标明每部电梯接下来响应哪些呼叫模拟器再据此推进事件。这个接口很干净换算法时只需要替换调度器内部实现模拟器一行不用改。文件职责关键类或函数model.py数据结构ElevatorState, Passengersimulator.py事件驱动仿真EventLoop, run()schedule.py基线策略fcfs(), scan()pso.py粒子群调度fitness(), pso_schedule()4.2 核心代码适应度函数、粒子更新与主调度循环适应度函数是整个 PSO 的心脏它决定一个粒子一组权重向量到底表现好不好。实现思路是把粒子解码成各电梯对当前呼叫的响应优先级然后推给简化仿真器用一段固定窗口模拟运行统计目标函数值。def fitness(p, calls, elevator_states, params): weights p # 每个电梯一个权重共 num_elevators 个 sim LightSimulator(elevator_states, calls, weights) sim.run() avg_wait sim.mean_wait_time() p95_wait sim.percentile_wait(95) energy sim.energy_consumption() return params[w1] * avg_wait params[w2] * p95_wait params[w3] * energy这段代码的关键在 LightSimulator它不追求完整物理仿真只把电梯按权重向量拆解成「先接哪些呼叫、后接哪些呼叫」的顺序然后粗算每段行程时间。weights 数组的长度等于电梯数量粒子群每个维度的取值代表对应电梯的积极程度。注意返回值是一个标量值越小越好因为粒子群默认是求最小化问题。粒子更新的标准公式就三行。def pso_update(particles, velocities, pbest, gbest, w, c1, c2, r1, r2): new_particles [] for i, p in enumerate(particles): velocity (w * velocities[i] c1 * r1 * (pbest[i] - p) c2 * r2 * (gbest - p)) new_pos p velocity new_particles.append(new_pos) return new_particles, velocityw 是惯性权重控制粒子保持原有速度的倾向c1 是自我认知系数引导粒子飞向自己的历史最优c2 是社会认知系数引导粒子飞向全局最优。r1、r2 是 0 到 1 的随机数用于保持随机性。参数调试时最常动的是 w固定 w 时粒子容易在后期产生很大速度越过最优点资源包采用线性递减策略w 从 0.9 降到 0.4前 60% 迭代探索、后 40% 收敛。主调度循环是每到一个决策点就调一次 PSO。def pso_schedule(state_snapshot, pending_calls, cfg): particles init_particles(cfg[num_particles], len(state_snapshot)) pbest particles.copy() gbest min(particles, keylambda p: fitness(p, pending_calls, state_snapshot, cfg)) for _ in range(cfg[max_iter]): for i, p in enumerate(particles): f fitness(p, pending_calls, state_snapshot, cfg) if f fitness(pbest[i], pending_calls, state_snapshot, cfg): pbest[i] p if f fitness(gbest, pending_calls, state_snapshot, cfg): gbest p particles, _ pso_update(particles, None, pbest, gbest, cfg[w], cfg[c1], cfg[c2], np.random.rand(), np.random.rand()) return decode(gbest, pending_calls)这段代码的意思是初始化一批随机权重向量评估一轮记录个体最优和全局最优然后循环更新。decode 函数把最优权重向量还原成具体的「电梯-呼叫」分配表。这里有个细节决策只针对当前待响应呼叫集合已经上梯的乘客不受影响这样能保证调度的稳定性不会因为每来一个乘客就把全盘方案推翻。4.3 参数怎么调粒子数、迭代数、惯性权重和惩罚系数粒子群效果好不好参数影响很大。资源包默认参数是经过多组实验磨合出来的但在你自己的场景里可能需要重新校准。参数默认值调整方向判断依据num_particles30电梯数超过 6 时增加到 50粒子太少搜不匀max_iter50响应时间充足时可加到 100迭代收益边际递减w0.9 → 0.4早熟就降低初始 w曲线是否提前停滞c1, c22.0, 2.0c2 大于 c1 偏向快速收敛是否长期振荡λ1, λ2, λ30.6, 0.25, 0.15候梯长尾严重就升 λ2P95 是否明显偏高调试顺序别贪多先固定粒子数和迭代数只调 w确认收敛曲线平滑后再调 c1、c2最后才动目标函数权重。调 w 时有个技巧如果最优适应度曲线下降后又反弹说明惯性权重衰减太快把衰减区间拉长即可。5. 电梯调度模型的常见问题与避坑五条实战踩坑记录5.1 平均候梯时间反向优化越优化P95 越长现象把 λ2 设为 0 之后跑粒子群平均候梯时间确实下降了但看分布图发现 95 分位候梯时间从 90 秒涨到 160 秒部分乘客被系统无限期冷落。原因平均指标本质上是把所有乘客的等待时间拉平系统发现短途乘客更容易「消化」于是优先服务短途乘客每个长距离呼叫都成了牺牲品。解决目标函数里至少保留一个长尾指标P95 或 max_wait_time且权重不要低于 0.2。从那以后我再也不信单一平均指标的调度方案。5.2 粒子群早熟迭代曲线卡住解和 SCAN 差不多现象粒子群迭代到第 15 代左右适应度就不再下降最终输出结果和 SCAN 基线几乎持平。原因惯性权重固定为 0.5粒子速度衰减过快所有粒子在早期就被吸引到同一个局部最优附近而且初始化粒子范围太窄只覆盖了权重向量的一小块区域。解决把 w 改成线性递减从 0.9 到 0.4同时初始化时在 [0.5, 2.0] 范围内随机撒点保证粒子分散。还有一个偏方是给全局最优加扰动每轮迭代有 5% 概率把 gbest 随机移动一个小区间避免所有粒子撞死在同一点。5.3 仿真和实测差 30%因为没人算加速度和开门时间现象模型预测平均候梯 60 秒实际系统跑出来 85 秒差距稳定在 30% 左右且楼层越高偏差越大。原因仿真里电梯每走一层都当成 1 秒匀速忽略了电梯启动加速度、制动减速度和每站开关门时间。高层行程里加减速时间占比还能接受但一旦频繁停靠停站时间会迅速累积。解决在行程时间计算公式里固定加 1.5 秒加减速成本和 3 秒开门成本停靠次数越多差值越明显。资源包的仿真器已经内置这两个时间常量如果你从零写代码千万别省这两个参数。5.4 基于客流「均匀假设」的模型总在午休翻车现象早高峰跑出漂亮结果到午休时段候梯时间突然恶化调度器频繁换向、空载率上升。原因早高峰客流方向单一目的楼层集中午休是双向客流目的楼层均匀分散用早高峰标定的权重直接套用午休场景系统无法适应模型切换。解决把一天切成多个调度时段每段单独生成客流参数并重新标定目标函数权重午休时段把 λ3 能耗权重降下去因为乘客更关注响应速度。资源包的 simulation config 里已经预留了 time_slots 数组按半小时切片填到达率就行。5.5 多目标加权系数拍脑袋结果甲之蜜糖乙之砒霜现象同一套 λ 参数在 A 栋楼表现很好换到 B 栋楼之后 P95 飙升到 200 秒。原因不同楼宇的电梯数量、层高、容量差异很大固定权重不可能通吃所有场景。解决做两轮灵敏度分析。第一轮固定客流让 λ1 从 0.3 扫到 0.9看 P95 的变化曲线第二轮固定权重让客流强度从 50% 变到 150%看平均候梯是否稳健。这张矩阵表做好之后再交付任何优化方案都心里有底。6. 进阶验证用蒙特卡洛随机客流检验调度器的鲁棒性先跑基线再跑优化算法是一套完整验证流程的核心。资源包里附带的 run_benchmark.py 就是为了这个目的准备的它对同一份客流数据分别跑 SCAN 和 PSO然后输出对比结果。但我建议你把单次对比扩展成蒙特卡洛验证——生成 20 组不同随机种子的客流每组都跑两个算法最后对比平均值和标准差。这一个步骤能帮你避免「靠运气取胜」的窘境。python run_benchmark.py --algo scan --seeds 20 python run_benchmark.py --algo pso --seeds 20seeds 参数控制随机客流生成器的种子。跑完后脚本会输出一个预测结果表包含每组的平均候梯时间和 P95并自动计算 20 组结果的平均值与标准差。标准差是判断鲁棒性的关键如果 PSO 的平均值低于 SCAN但标准差是 SCAN 的三倍说明这个算法只在特定客流模式下有效不适合作为通用方案。我自己评估一份电梯调度资源时还会要求脚本输出每次调度的空载率和轿厢平均载客量。这两个指标不直接出现在目标函数里但能解释算法为什么有效空载率低说明电梯没有盲目跑空车平均载客量稳定说明乘客没有被集中堆到某一台电梯上。用这些辅助指标和候梯时间交叉验证结论才站得住。这套资源里的代码和仿真脚本已经整理打包放在下载区可以直接拿走连文件结构都维持了我在上面描述的样子。拿到之后建议先跑一遍 SCAN 基线再打开 pso.py 调参数整套流程走完你对电梯调度问题的理解会扎实很多。从那以后我每次做调度类选题不管对象是电梯、AGV 还是外卖骑手都强制自己走一遍「基线对照 随机客流 灵敏度分析」这套习惯帮我避开了不少坑。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑