ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

python的运筹学工业场景模拟第九十九篇:厂区班车调度,多个厂区站点,班车座位上限,规划发车班次,减少班车运营总成本。

python的运筹学工业场景模拟第九十九篇:厂区班车调度,多个厂区站点,班车座位上限,规划发车班次,减少班车运营总成本。 厂区班车“算着跑”用整数规划把年度通勤支出压低 22%“某汽车零部件工厂横跨 3 个厂区12 个候车站点员工 1200 人。以前行政按经验排班固定 8 辆 50 座大巴每天跑 4 趟年运营费 216 万高峰仍挤不上车员工投诉不断。后来我用 Python 写了个厂区班车调度优化器0.8 秒算完全年最优发车方案减到 6 辆车每天跑 3 趟年费降到 168 万省了 48 万零投诉。行政总监说‘原来不是车不够是没算明白怎么跑。’”—— 参考北京理工大学《运筹学》第 4 章“整数规划”、第 6 章“运输问题”一、实际应用场景描述厂区班车调度优化器是任何涉及“多点接送、运力受限、成本敏感”场景的“调度大脑”。凡是“车要跑、人要坐、钱要省”的地方都是它行业 典型场景 约束条件 痛点汽车制造 跨厂区通勤 座位上限、发车间隔 运力不足、成本高化工园区 倒班接送 夜班时段、安全间距 安全隐患、效率低电子厂 宿舍-车间接驳 潮汐人流、错峰下班 高峰拥挤、低谷空跑钢铁厂 高温作业接送 防暑降温、快速转运 等待时间长、体验差物流园 分拣中心摆渡 高频次、短距离 车辆闲置、调度混乱科技园 地铁接驳专线 早晚高峰、弹性需求 准点率低、投诉多核心矛盾- 运筹学教科书教“整数规划0-1变量、约束条件、目标函数”- 行政拿到的是“员工住址分布、打卡时间、车辆档案”- 调度员凭经验“固定班次、固定路线”- 结果要么车不够坐要么车空着跑成本居高不下。┌──────────────────────────────────────────────────────────────┐│ 厂区班车调度优化器 · 调度大脑 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 12个候车站点 │││ │ • A厂区南门: 早高峰需求180人 │││ │ • B厂区食堂: 早高峰需求150人 │││ │ • C厂区宿舍: 早高峰需求220人 │││ │ • ...共12个站点 │││ │ │││ │ 车辆资源: │││ │ • 50座大巴 × 8辆 → 优化后 × 6辆 │││ │ • 单车载客上限50人 │││ │ • 单趟运营成本400元 │││ │ │││ │ 整数规划逻辑: │││ │ 1. 决策变量: x[i][k] 第k趟车是否停靠站点i │││ │ 2. 目标函数: min Σ(发车趟数 × 单趟成本) │││ │ 3. 约束1: 每个站点至少被服务1次 │││ │ 4. 约束2: 每趟车载客量 ≤ 50人 │││ │ 5. 约束3: 发车时间满足上班截止时间 │││ │ │││ │ 输出: │││ │ • 最优发车方案(几趟车、每趟停哪些站) │││ │ • 运力分配表(每趟车载多少人) │││ │ • 成本分析报告(车辆费空驶费) │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 行政总监: 想知道最少几辆车能跑完所有站点 │││ • 教科书: 整数规划输出0-1决策变量、松弛变量 │││ • 现场: 12个站点、1200名员工、50座大巴 │││ • 本程序: 把数学规划变成司机能看懂的路单 │││ ││ 【本程序处理流程】 │││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐│││ │ 加载站点 │──►│ 构建整数 │──►│ PuLP求解 │──►│ 生成路单 ││││ │ 需求数据 │ │ 规划模型 │ │ 最优方案 │ │ 与报表 ││││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某汽车零部件厂行政总监的原话“我们厂横跨 3 个厂区冲压、焊接、总装12 个候车站点宿舍区、食堂、各车间门口。全厂 1200 名一线员工倒班制早8晚8两班倒。以前我们调度有个死规矩- ‘固定 8 辆车’不管淡旺季永远 8 辆 50 座大巴- ‘固定 4 趟’早高峰 6:30、7:00、7:30、8:00 各发一趟- ‘固定路线’每趟车绕遍所有站点宁可空跑也不跳过。结果就是- 早高峰 7:30 那趟挤不上车实际载客 68 人超载 36%- 晚高峰 20:00 那趟空荡荡实际载客 12 人空载率 76%- 去年车队运营费 216 万车辆折旧油费司机工资- 员工投诉 47 起主要是挤不上车、迟到扣款。厂长问我‘8 辆车1200 人怎么就拉不完’我也很委屈早高峰人多、晚高峰人少固定班次根本不匹配。不是车不够是没算明白怎么跑。后来我研究北理工《运筹学》第 4 章‘整数规划’才发现这是个标准的“集合覆盖车辆路径”混合问题。- 决策变量第 k 趟车是否停靠站点 ix[i][k] 0或1- 目标函数最小化总发车趟数等价于最小化车辆数- 约束1每个站点至少被服务 1 次覆盖所有员工- 约束2每趟车载客量 ≤ 50 人不超载- 约束3所有车次需在 8:00 前完成运送时间窗。我写了个 Python 厂区班车调度优化器——0.8 秒算完全年最优发车方案- 早高峰增开趟次7:00、7:20、7:40 三趟密集发车- 晚高峰压缩趟次20:00 仅发 1 趟- 车辆从 8 辆减到 6 辆每天从 4 趟减到 3 趟- 年运营费从 216 万压到 168 万省 48 万- 全年零投诉再没出现超载。行政总监看完说‘原来不是车不够是没算明白怎么跑。这 0.8 秒的计算值 50 万。’”2.2 经验调度 vs 整数规划优化量化对比指标 经验调度固定班次 整数规划优化 改善效果运营车辆数 8 辆 6 辆 -25%日均发车趟次 4 趟/天 3 趟/天 -25%年运营费用 216 万/年 168 万/年 -22%单车日均里程 120 km 95 km -21%高峰超载率 36%最高68人/50座 0% 消除低谷空载率 76% 32% -58%员工投诉量 47 起/年 0 起/年 消除决策耗时 3 天/季度人工调整 0.8 秒/年 -99.99%关键发现班车调度的瓶颈不在“车的数量”而在“路线的匹配度”。整数规划把“固定班次”变成“按需发车”让每一辆车都跑在需求最密集的时候。三、核心逻辑讲解大白话版3.1 用大白话解释“厂区班车调度问题”想象你要组织一场大型婚礼有 12 桌客人要从酒店接到婚礼现场- 每桌人数不同有的桌 10 人有的桌 15 人总共 120 人- 你有几辆大巴每辆最多坐 50 人- 司机师傅很死板每辆车必须走固定路线不能灵活调整- 婚礼不能迟到所有人必须在 8:00 前到场- 租车很贵每辆车跑一趟 400 元多跑一趟多花 400。问题是最少派几辆车怎么安排路线花钱最少整数规划就是帮你算这个的“智能调度员”1. 先想“有哪些选择”决策变量- 第 1 辆车停不停 1 号桌是/否用 0 和 1 表示- 第 2 辆车停不停 2 号桌是/否- ……这就是0-1 决策变量。2. 再想“要花多少钱”目标函数- 派 1 辆车 花 400 元- 派 2 辆车 花 800 元- 目标就是派的车越少越好花钱最少。3. 然后想“有什么规矩”约束条件- 规矩1每桌客人至少被一辆车接走不能落下人- 规矩2每辆车不能超过 50 人不能超载- 规矩3所有车 8:00 前要到场时间约束。- 这些“规矩”就是约束条件。4. 最后想“怎么算最快”求解算法- 暴力枚举尝试所有可能的派车方案太慢12 辆车有百万种组合- 整数规划用数学方法直接找最优解快0.8 秒搞定。大白话逻辑- “派不派车” → 0-1 决策变量- “花钱最少” → 目标函数- “不落下人、不超载” → 约束条件- “智能调度员” → 整数规划求解器。工业现场版- 婚礼客人 各站点员工- 大巴车 班车- 酒店 候车站点- 婚礼现场 厂区大门- 租车费 班车运营成本- 智能调度员 厂区班车调度优化器。3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 4 章“整数规划”、第 6 章“运输问题”厂区班车调度整数规划模型集合定义- I \{1,2,\dots,n\} 站点集合 n12 - K \{1,2,\dots,m\} 最大车次集合 m10 预留冗余。参数- d_i 站点 i 的早高峰需求人数- C 单车最大载客量50 人- F 单车单趟固定成本400 元- T_{max} 最晚到达时间8:00转换为分钟。决策变量- x_{ik} \in \{0,1\} 第 k 趟车是否停靠站点 i - y_k \in \{0,1\} 第 k 趟车是否发车- z_{ik} \geq 0 第 k 趟车在站点 i 的上客人数。目标函数最小化总运营成本\min Z \sum_{k1}^{m} F \cdot y_k约束条件1. 覆盖约束每个站点至少被服务一次\sum_{k1}^{m} x_{ik} \geq 1, \quad \forall i \in I2. 载客量约束每趟车不超载\sum_{i1}^{n} z_{ik} \leq C \cdot y_k, \quad \forall k \in K3. 需求满足约束上客人数不超过站点需求\sum_{k1}^{m} z_{ik} d_i, \quad \forall i \in I4. 变量关联约束 z_{ik} 非零则 x_{ik} 必须为 1z_{ik} \leq d_i \cdot x_{ik}, \quad \forall i \in I, \forall k \in K5. 时间窗约束简化版假设站点间行驶时间已知\sum_{i1}^{n} t_i \cdot x_{ik} \leq T_{max}, \quad \forall k \in K其中 t_i 为站点 i 的服务时间行驶时间北理工教材要点- 第 4 章 §4.1整数规划的数学模型0-1 变量、目标函数、约束条件- 第 4 章 §4.30-1 型整数规划集合覆盖问题- 第 6 章 §6.1运输问题的数学模型供需平衡- 本程序将集合覆盖与运输问题结合形成班车调度专用模型。3.3 如何映射到代码中业务逻辑 Python 代码PuLP站点定义BusStop 数据类决策变量 x_{ik}pulp.LpVariable.dicts(stop_served, ...)决策变量 y_kpulp.LpVariable.dicts(trip_used, ..., catBinary)决策变量 z_{ik}pulp.LpVariable.dicts(passengers, ..., lowBound0)目标函数prob pulp.lpSum(fixed_cost * y[k] ...)覆盖约束prob pulp.lpSum(x[(i, k)] for k in trips) 1载客量约束prob pulp.lpSum(z[(i, k)] for i in stops) capacity * y[k]求解器调用prob.solve(pulp.PULP_CBC_CMD(msgFalse))四、OOP 代码实现精简可运行4.1 项目结构shuttle_scheduler/├── shuttle_scheduler.py # 核心代码单文件~450行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary厂区班车调度优化器 · 调度大脑参考: 北理工《运筹学》第4章整数规划、第6章运输问题功能:1. 定义候车站点、车辆参数、需求分布2. 构建整数规划调度模型(集合覆盖运输问题)3. 使用PuLP求解最优发车方案4. 统计运力分配、成本构成、服务水平运行:python shuttle_scheduler.py(需要安装pulp, numpy, pandas)注意:本程序解决厂区班车调度优化问题, 属于整数规划的典型应用。对于超大规模问题(站点50, 车辆30), 建议使用列生成(CG)或启发式算法。import pulpimport numpy as npimport pandas as pdfrom dataclasses import dataclass, fieldfrom typing import List, Dict, Tuple, Optional, Anyfrom enum import Enumimport itertoolsimport timefrom collections import defaultdict# ─── 枚举与常量 ────────────────────────────────────────────────────────────class ShiftType(Enum):班次类型MORNING 早高峰 # 上班时段EVENING 晚高峰 # 下班时段NIGHT 夜班 # 夜班接送# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass BusStop:候车站点stop_id: strname: strdemand: int # 该站点需求人数service_time: float 2.0 # 停靠服务时间(分钟)travel_times: Dict[str, float] field(default_factorydict) # 到其他站点的行驶时间def __str__(self):return f{self.name}({self.stop_id}): 需求{self.demand}人, 服务{self.service_time}分钟dataclassclass Bus:班车bus_id: strcapacity: int 50 # 额定载客量fixed_cost: float 400.0 # 单趟固定成本(元)variable_cost: float 2.0 # 可变成本(元/公里, 暂未使用)def __str__(self):return f班车{self.bus_id}: {self.capacity}座, 单趟{self.fixed_cost}元dataclassclass ShuttleSchedule:班车调度方案success: booltotal_cost: float # 总运营成本num_trips: int # 发车趟次trips: Dict[int, List[str]] # 每趟车的停靠站点列表passengers: Dict[Tuple[str, int], int] # (站点ID, 车次ID) - 上客人数utilization: Dict[int, float] # 每趟车的利用率solve_time: floatsolver_status: strpropertydef avg_utilization(self) - float:平均车辆利用率if not self.utilization:return 0.0return sum(self.utilization.values()) / len(self.utilization)propertydef total_passengers(self) - int:总运送人数return sum(self.passengers.values())propertydef cost_per_passenger(self) - float:人均成本total self.total_passengersreturn self.total_cost / total if total 0 else 0.0# ─── 厂区班车调度优化器 ───────────────────────────────────────────────────class ShuttleScheduler:厂区班车调度优化器(基于整数规划)def __init__(self,stops: List[BusStop],buses: List[Bus],shift_type: ShiftType ShiftType.MORNING,max_trips: int 10,time_limit: int 60):Args:stops: 候车站点列表buses: 可用班车列表shift_type: 班次类型max_trips: 最大允许发车趟次time_limit: 求解时间限制(秒)self.stops stopsself.buses busesself.shift_type shift_typeself.max_trips max_tripsself.time_limit time_limit# 使用第一辆车的参数作为标准(假设车队同质)self.capacity buses[0].capacityself.fixed_cost buses[0].fixed_cost# 站点索引映射self.stop_indices {stop.stop_id: i for i, stop in enumerate(stops)}self.num_stops len(stops)# 求解结果self.problem Noneself.solution Nonedef build_model(self) - pulp.LpProblem:构建整数规划模型print( 构建整数规划模型...)print(f • 站点数量: {self.num_stops}个)print(f • 最大发车趟次: {self.max_trips}趟)print(f • 单车容量: {self.capacity}人)print(f • 单趟成本: {self.fixed_cost}元)# 创建优化问题(最小化成本)prob pulp.LpProblem(Shuttle_Scheduling_Optimization, pulp.LpMinimize)# 决策变量# x[i,k]: 第k趟车是否停靠站点i (0-1变量)x pulp.LpVariable.dicts(stop_served,((i, k) for i in range(self.num_stops) for k in range(self.max_trips)),catBinary)# y[k]: 第k趟车是否发车 (0-1变量)y pulp.LpVariable.dicts(trip_used,(k for k in range(self.max_trips)),catBinary)# z[i,k]: 第k趟车在站点i的上客人数 (非负整数)z pulp.LpVariable.dicts(passengers,((i, k) for i in range(self.num_stops) for k in range(self.max_trips)),lowBound0,catInteger)# 目标函数: 最小化总发车成本prob pulp.lpSum(self.fixed_cost * y[k] for k in range(self.max_trips))# 约束1: 每个站点至少被服务一次(覆盖约束)for i in range(self.num_stops):prob pulp.lpSum(x[(i, k)] for k in range(self.max_trips)) 1# 约束2: 每趟车的载客量不超过容量for k in range(self.max_trips):prob pulp.lpSum(z[(i, k)] for i in range(self.num_stops)) self.capacity * y[k]# 约束3: 每个站点的需求必须被完全满足for i in range(self.num_stops):prob pulp.lpSum(z[(i, k)] for k in range(self.max_trips)) self.stops[i].demand# 约束4: 上客人数非零则必须停靠该站点for i in range(self.num_stops):for k in range(self.max_trips):prob z[(i, k)] self.stops[i].demand * x[(i, k)]# 约束5: 简化时间窗约束(假设站点按顺序服务)# 实际项目中应根据具体路网计算行驶时间# 此处简化为: 每趟车服务站点数不超过阈值for k in range(self.max_trips):prob pulp.lpSum(x[(i, k)] for i in range(self.num_stops)) 8print(f 模型构建完成: {self.num_stops * self.max_trips}个0-1变量, {self.max_trips}个连续变量)return prob, x, y, zdef solve(self) - ShuttleSchedule:求解优化模型start_time time.perf_counter()# 构建模型prob, x, y, z self.build_model()self.problem probprint( 开始求解...)# 设置求解器参数solver pulp.PULP_CBC_CMD(msgFalse, # 不显示求解日志timeLimitself.time_limit,gapRel0.01, # 相对间隙1%threads4 # 使用4线程)# 求解prob.solve(solver)end_time time.perf_counter()solve_time end_time - start_time# 检查求解状态status pulp.LpStatus[prob.status]print(f ✅ 求解完成! 状态: {status}, 耗时: {solve_time:.3f}秒)if prob.status ! pulp.LpOptimal:print(f ⚠️ 警告: 未找到最优解, 状态: {status})return ShuttleSchedule(successFalse,total_cost0.0,num_trips0,trips{},passengers{},utilization{},solve_timesolve_time,solver_statusstatus)# 提取解决方案trips defaultdict(list)passengers {}utilization {}total_cost pulp.value(prob.objective)actual_trips 0for k in range(self.max_trips):if y[k].varValue 0.5: # 该趟车发车actual_trips 1trip_load 0for i in range(self.num_stops):if x[(i, k)].varValue 0.5: # 停靠该站点stop_id self.stops[i].stop_idtrips[k].append(stop_id)# 获取上客人数pax_count int(z[(i, k)].varValue)if pax_count 0:passengers[(stop_id, k)] pax_counttrip_load pax_count# 计算利用率utilization[k] trip_load / self.capacity if self.capacity 0 else 0print(f ▶ 第{k1}趟: {trip_load}人, 停靠{trips[k]}, 利用率{utilization[k]*100:.1f}%)print(f 最优方案: {actual_trips}趟车, 总成本{total_cost:.1f}元)return ShuttleSchedule(successTrue,total_costtotal_cost,num_tripsactual_trips,tripsdict(trips),passengerspassengers,utilizationutilization,solve_timesolve_time,solver_statusstatus)def heuristic_schedule(self) - ShuttleSchedule:启发式调度(作为对比)print( 启发式调度(经验规则)...)start_time time.perf_counter()# 贪婪算法: 每次选择需求最大的站点组合, 直到覆盖所有需求remaining_demand {stop.stop_id: stop.demand for stop in self.stops}trips {}passengers {}utilization {}total_cost 0trip_id 0while sum(remaining_demand.values()) 0 and trip_id self.max_trips:# 按剩余需求排序站点sorted_stops sorted([(sid, demand) for sid, demand in remaining_demand.items() if demand 0],keylambda x: x[1],reverseTrue)if not sorted_stops:break# 构建一趟车: 尽可能多地装载乘客current_load 0trip_stops []for stop_id, demand in sorted_stops:if current_load demand self.capacity:trip_stops.append(stop_id)passengers[(stop_id, trip_id)] demandcurrent_load demandremaining_demand[stop_id] 0if trip_stops:trips[trip_id] trip_stopsutilization[trip_id] current_load / self.capacitytotal_cost self.fixed_costtrip_id 1end_time time.perf_counter()solve_time end_time - start_timeprint(f ✅ 启发式调度完成! 耗时: {solve_time:.3f}秒)print(f 方案: {len(trips)}趟车, 总成本{total_cost:.1f}元)return ShuttleSchedule(successTrue,total_costtotal_cost,num_tripslen(trips),tripstrips,passengerspassengers,utilizationutilization,solve_timesolve_time,solver_statusHeuristic)# ─── 结果分析器 ───────────────────────────────────────────────────────────class ScheduleAnalyzer:调度结果分析器def __init__(self):passdef generate_schedule_table(self, schedule: ShuttleSchedule) - pd.DataFrame:生成调度时刻表data []for trip_id in sorted(schedule.trips.keys()):stops → .join(schedule.trips[trip_id])load sum(schedule.passengers.get((sid, trip_id), 0)for sid in schedule.trips[trip_id])util schedule.utilization.get(trip_id, 0) * 100data.append({车次: f第{trip_id1}趟,停靠站点: stops,载客量(人): load,利用率(%): f{util:.1f}%,状态: 发车 if util 0 else 停运})return pd.DataFrame(data)def print_detailed_report(self,optimal_schedule: ShuttleSchedule,heuristic_schedule: ShuttleSchedule,stops: List[BusStop]):打印详细对比报告print(\n *80)print(厂区班车调度优化详细报告)print(*80)print(f\n 总体对比:)print(f • 整数规划方案: {optimal_schedule.num_trips}趟车, {optimal_schedule.total_cost:.1f}元)print(f • 启发式方案: {heuristic_schedule.num_trips}趟车, {heuristic_schedule.total_cost:.1f}元)print(f • 节省金额: {heuristic_schedule.total_cost - optimal_schedule.total_cost:.1f}元)print(f • 节省比例: {(heuristic_schedule.total_cost - optimal_schedule.total_cost)/heuristic_schedule.total_cost*100:.1f}%)print(f\n 运力分析:)print(f • 整数规划平均利用率: {optimal_schedule.avg_utilization*100:.1f}%)print(f • 启发式平均利用率: {heuristic_schedule.avg_utilization*100:.1f}%)print(f • 总运送人数: {optimal_schedule.total_passengers}人)print(f • 人均成本: {optimal_schedule.cost_per_passenger:.2f}元/人)print(f\n⏱️ 性能分析:)print(f • 整数规划求解时间: {optimal_schedule.solve_time:.3f}秒)print(f • 启发式求解时间: {heuristic_schedule.solve_time:.3f}秒)print(f • 求解状态: {optimal_schedule.solver_status})print(f\n 优化建议:)if optimal_schedule.avg_utilization 0.7:print(f • 平均利用率偏低({optimal_schedule.avg_utilization*100:.1f}%), 可考虑合并站点或调整需求预测)if optimal_schedule.num_trips len(stops) * 0.8:print(f • 发车趟次较多({optimal_schedule.num_trips}趟), 可能存在站点分散问题)# 计算站点覆盖率covered_stops set()for trip_stops in optimal_schedule.trips.values():covered_stops.update(trip_stops)coverage_rate len(covered_stops) / len(stops) * 100print(f • 站点覆盖率: {coverage_rate:.1f}%利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表