
1. 赛题核心与我的解题思路复盘最近整理资料翻到了2021年参加华数杯数学建模竞赛A题“电动汽车无线充电优化匹配研究”的文档。虽然过去几年了但当时为了这道题熬的夜、掉的头发还有最终把模型跑通、论文写完那一刻的成就感现在想起来依然很清晰。这道题非常典型它把一个前沿的工程问题电动汽车无线充电抽象成了一个经典的优化模型考察的不仅是数学建模能力更是将实际问题转化为数学模型并用编程工具求解的完整链路。今天我就以一个过来人的身份把这道题的解题思路、核心模型、代码实现的关键细节以及我们团队当时踩过的坑和获奖心得系统地复盘分享给大家。无论你是正在备战数模的新手还是对优化问题感兴趣的朋友相信这篇长文都能给你带来一些实实在在的启发。这道题的核心场景并不复杂在一个大型停车场或充电站里有多辆需要充电的电动汽车和多台无线充电桩。每辆车有自己的停车位置、电池容量、当前电量、目标电量以及计划离开时间每个充电桩也有其固定的位置和输出功率。问题就在于如何动态地为这些车辆分配合适的充电桩并规划每辆车的充电时间段使得在满足所有车辆基本充电需求的前提下整个系统的总充电耗时最短、或者总能耗最低、或者充电桩的利用率最高这本质上是一个带有时间窗和空间约束的资源调度与路径规划混合问题。我们当时拿到题目第一感觉就是它融合了整数规划、排队论和图论的一些思想非常适合用Python结合优化库来求解。下面我就分步骤拆解我们当时的思考与实现过程。2. 问题一静态场景下的最优匹配模型构建问题一通常设定为一个静态场景即在某一固定时刻所有车辆和充电桩的状态位置、电量等已知且车辆一旦开始充电就不再移动。我们需要给出一个当前时刻的最优匹配方案。这是整个赛题的基础。2.1 模型抽象与关键假设首先我们要把文字描述转化为数学语言。我们定义了以下集合和参数集合I {1, 2, ..., m}表示电动汽车集合J {1, 2, ..., n}表示无线充电桩集合。关键参数车辆i的电池容量C_i(kWh)当前电量SOC_i(%)目标电量SOC_i_target(%)因此所需充电量E_i_needed C_i * (SOC_i_target - SOC_i) / 100(kWh)。充电桩j的额定输出功率P_j(kW)。这是一个关键参数功率不同充电时间差异巨大。车辆i到充电桩j的距离d_ij。这里需要做一个重要假设题目通常不会给出精确的停车场地图所以我们需要根据车辆和充电桩的坐标题目可能给出也可能需要自己合理假设用欧几里得距离或曼哈顿距离来计算d_ij。我们假设车辆可以无障碍移动到充电桩位置这个距离主要用来评估移动成本或约束比如距离过远则不匹配。一个隐含但至关重要的参数充电效率η。无线充电存在能量损耗效率通常在90%-95%之间。我们假设它是一个常数例如η0.92。那么车辆i在充电桩j上充电所需的理论时间为T_ij E_i_needed / (P_j * η)。这是最核心的计算式。有了这些我们就可以定义决策变量了。最自然的想法是使用0-1 决策变量x_ij 1表示将车辆i分配给充电桩j进行充电否则x_ij 0。2.2 目标函数与约束条件的设计目标函数是模型的指挥棒。题目可能要求“总充电时间最短”、“总能耗最低”或“服务车辆最多”。我们以最常见的“总充电时间最短”为例。目标函数最小化总充电时间这里需要注意“总充电时间”的定义。如果所有充电桩并行工作那么总时间应该是最后一个结束充电的车辆所花费的时间即最大完成时间Makespan。但更常见的、也符合实际调度逻辑的理解是所有车辆充电时间的总和假设时间成本可叠加。我们采用后者因为它数学上更易于处理且能反映整体效率。Minimize Z Σ_i Σ_j (T_ij * x_ij)接下来是约束条件这是模型符合实际情况的保证每辆车最多被分配到一个充电桩Σ_j x_ij ≤ 1, ∀i ∈ I。注意这里是“≤1”而不是“1”因为可能存在车辆过多、充电桩不足的情况部分车辆可能无法被立即服务这引出了“等待”或“排队”的概念在问题二中会更突出。每个充电桩最多同时服务一辆车Σ_i x_ij ≤ 1, ∀j ∈ J。这是无线充电桩的物理限制。充电量需求约束这个约束已经隐含在T_ij的计算中。如果我们引入另一个表示实际充电时间的连续变量则需要此约束。距离约束可选可以添加x_ij * d_ij ≤ D_max即只有当车辆与充电桩距离小于最大允许距离D_max时才能分配。这可以防止不合理的远距离匹配。变量类型约束x_ij ∈ {0, 1}。这样我们就得到了一个标准的0-1 整数规划模型。对于小规模问题几十辆车几十个桩可以直接用PuLP、ortools或商业求解器Gurobi、CPLEX的Python接口来求解。2.3 Python实现要点与代码片段我们当时选择了PuLP库因为它开源免费API 简洁。下面是核心建模部分的代码片段import pulp import numpy as np # 假设我们有数据 num_cars 10 num_piles 5 # 随机生成车辆和充电桩数据实际应从题目文件读取 np.random.seed(2021) car_capacity np.random.uniform(40, 80, num_cars) # 电池容量单位kWh car_soc np.random.uniform(20, 50, num_cars) # 当前电量百分比 car_target_soc np.random.uniform(80, 95, num_cars) # 目标电量百分比 pile_power np.random.uniform(7, 22, num_piles) # 充电桩功率单位kW模拟慢充和快充 # 计算所需电量 energy_needed car_capacity * (car_target_soc - car_soc) / 100.0 # 假设效率 eta 0.92 # 计算充电时间矩阵 (cars x piles) # 如果功率为0时间设为无穷大表示不能充 time_matrix np.zeros((num_cars, num_piles)) for i in range(num_cars): for j in range(num_piles): if pile_power[j] 0: time_matrix[i, j] energy_needed[i] / (pile_power[j] * eta) else: time_matrix[i, j] 1e6 # 一个大数代表不可行 # 创建问题实例 prob pulp.LpProblem(EV_Wireless_Charging_Assignment, pulp.LpMinimize) # 创建决策变量字典 x pulp.LpVariable.dicts(assign, ((i, j) for i in range(num_cars) for j in range(num_piles)), lowBound0, upBound1, catBinary) # 设置目标函数 prob pulp.lpSum([time_matrix[i, j] * x[(i, j)] for i in range(num_cars) for j in range(num_piles)]) # 添加约束每辆车最多分配一个桩 for i in range(num_cars): prob pulp.lpSum([x[(i, j)] for j in range(num_piles)]) 1 # 添加约束每个桩最多服务一辆车 for j in range(num_piles): prob pulp.lpSum([x[(i, j)] for i in range(num_cars)]) 1 # 求解问题 solver pulp.PULP_CBC_CMD(msgFalse) # 使用CBC求解器不输出日志 prob.solve(solver) # 打印结果 print(f求解状态: {pulp.LpStatus[prob.status]}) print(f最小化总时间目标值: {pulp.value(prob.objective):.2f} 小时) # 提取分配方案 assignment [] for i in range(num_cars): for j in range(num_piles): if pulp.value(x[(i, j)]) 1: assignment.append((i, j, time_matrix[i, j])) print(f车辆 {i} - 充电桩 {j}, 预计充电时间: {time_matrix[i, j]:.2f} 小时)注意上面的代码是一个高度简化的示例。实际比赛中数据读取、距离矩阵的计算、对无解情况的处理比如车多桩少时如何定义“部分车辆等待”、以及结果的可视化用matplotlib画出匹配图都是必须完成的环节。我们当时花了大量时间在数据预处理和结果展示上。3. 问题二动态调度与排队模型问题一解决了“一瞬间”的匹配问题。但现实是车辆是陆续到达和离开的这是一个动态过程。问题二通常要求我们考虑一段时间内的车辆到达序列设计动态调度策略。3.1 从静态匹配到动态事件驱动动态调度的核心是时间轴和事件。我们需要模拟一个时间流逝的过程关键事件包括“车辆到达”、“车辆开始充电”、“车辆充电结束离开”。当这些事件发生时系统状态哪些桩空闲、哪些车在等待发生变化我们需要重新决策。一种经典的思路是离散事件仿真。我们维护一个优先队列heapq来管理未来事件队列按事件发生时间排序。仿真的主循环就是不断从队列中取出最早的事件进行处理并可能产生新的事件加入队列。3.2 调度策略的设计与对比当一辆车到达时如果有空闲充电桩如何分配如果没有空闲桩车辆进入等待队列当有桩空闲时又从等待队列中按什么规则选择车辆这就是调度策略。常见的策略有先到先服务FCFS最简单按到达顺序分配。实现容易但可能不是最优。最短充电时间优先SPT优先给充电时间短的车充电。这能最小化平均等待时间让更多车快速完成充电但可能导致充电时间长的车“饿死”。最早截止时间优先EDF如果题目给了车辆的计划离开时间那么优先给截止时间早的车充电。这能减少违约。最大充电需求优先LPT与SPT相反有时为了整体效率先解决“大块头”问题。基于问题一模型的滚动优化这是一个更高级的策略。在每个决策点有车到达或有桩空闲我们将当前所有已到达但未开始充电的车辆和所有空闲充电桩构成一个当前时刻的静态匹配子问题然后用问题一的模型可能修改目标函数如最小化总完成时间求解得到立即执行的分配方案。我们团队当时采用了滚动优化结合FCFS保障公平性的混合策略。具体来说我们设定一个时间窗口每隔一段时间比如每15分钟或者每当空闲桩数量达到一定阈值时就触发一次基于当前等待车辆的优化匹配。而在两次优化之间新到达的车辆则按FCFS规则插入等待队列。这样既利用了优化模型提升效率又避免了某些车辆过长时间等待。3.3 Python仿真框架搭建搭建仿真框架是问题二的难点也是代码量最大的部分。下面勾勒一个简化的框架结构import heapq import numpy as np class ChargingPile: def __init__(self, pile_id, power): self.id pile_id self.power power self.is_busy False self.serving_car None self.finish_time None class ElectricVehicle: def __init__(self, car_id, arrive_time, capacity, soc, target_soc): self.id car_id self.arrive_time arrive_time self.capacity capacity self.soc soc self.target_soc target_soc self.energy_needed capacity * (target_soc - soc) / 100.0 self.start_charge_time None self.leave_time None self.assigned_pile None class Event: 事件类type: arrive, start, finish def __init__(self, time, event_type, vehicleNone, pileNone): self.time time self.type event_type self.vehicle vehicle self.pile pile def __lt__(self, other): return self.time other.time # 用于优先队列排序 class ChargingStationSimulator: def __init__(self, piles, strategyFCFS): self.piles piles self.waiting_queue [] # 等待队列 self.strategy strategy self.event_queue [] # 优先队列存储未来事件 self.current_time 0 self.served_cars [] heapq.heapify(self.event_queue) def add_event(self, event): heapq.heappush(self.event_queue, event) def run(self, car_arrival_sequence): # 初始化将所有车辆到达事件加入队列 for car in car_arrival_sequence: self.add_event(Event(car.arrive_time, arrive, vehiclecar)) while self.event_queue: current_event heapq.heappop(self.event_queue) self.current_time current_event.time if current_event.type arrive: self._handle_arrival(current_event.vehicle) elif current_event.type finish: self._handle_finish(current_event.pile) def _handle_arrival(self, car): # 寻找空闲充电桩 free_pile self._find_free_pile() if free_pile is not None: self._assign_car_to_pile(car, free_pile) else: # 无空闲桩加入等待队列根据策略排序 self._add_to_waiting_queue(car) def _handle_finish(self, pile): car pile.serving_car car.leave_time self.current_time self.served_cars.append(car) # 释放充电桩 pile.is_busy False pile.serving_car None # 检查等待队列分配下一个车辆 if self.waiting_queue: next_car self._select_next_car_from_queue() # 根据策略选择 self._assign_car_to_pile(next_car, pile) def _assign_car_to_pile(self, car, pile): # 计算充电时间 charge_time car.energy_needed / (pile.power * 0.92) # 更新状态 pile.is_busy True pile.serving_car car pile.finish_time self.current_time charge_time car.start_charge_time self.current_time car.assigned_pile pile # 创建充电结束事件 self.add_event(Event(pile.finish_time, finish, pilepile)) def _find_free_pile(self): for pile in self.piles: if not pile.is_busy: return pile return None def _add_to_waiting_queue(self, car): # 根据策略插入队列。例如FCFS直接append if self.strategy FCFS: self.waiting_queue.append(car) elif self.strategy SPT: # 按所需充电时间排序 self.waiting_queue.append(car) self.waiting_queue.sort(keylambda c: c.energy_needed) # ... 其他策略 def _select_next_car_from_queue(self): # 根据策略从队列头部选择车辆 if self.waiting_queue: car self.waiting_queue.pop(0) return car return None # 使用示例 if __name__ __main__: # 创建充电桩 piles [ChargingPile(i, np.random.uniform(7, 22)) for i in range(5)] # 生成车辆到达序列 (示例) cars [] for i in range(20): arrive_time np.random.exponential(30) # 平均30分钟来一辆车 if i 0: arrive_time cars[-1].arrive_time # 累积时间 cars.append(ElectricVehicle(i, arrive_time, np.random.uniform(40,80), np.random.uniform(20,50), np.random.uniform(80,95))) # 创建模拟器并运行 sim ChargingStationSimulator(piles, strategyFCFS) sim.run(cars) # 分析结果如平均等待时间、充电桩利用率等 total_wait_time sum([c.start_charge_time - c.arrive_time for c in sim.served_cars if c.start_charge_time]) avg_wait total_wait_time / len(sim.served_cars) print(f平均等待时间: {avg_wait:.2f} 分钟)这个框架提供了动态仿真的骨架。在实际比赛中我们需要在此基础上实现不同的调度策略_add_to_waiting_queue和_select_next_car_from_queue方法并设计丰富的评价指标如平均等待时间、充电桩平均利用率、系统吞吐量、车辆平均逗留时间等来对比策略优劣。图表输出如等待时间分布图、充电桩忙闲时序图也是论文的加分项。4. 问题三模型拓展、灵敏度分析与论文写作问题三通常是开放性的要求对模型进行拓展、做灵敏度分析或者讨论实际应用中的其他因素。这部分最能体现团队的创新思维和对问题的深入理解。4.1 模型拓展方向考虑充电效率非线性实际上无线充电效率并非恒定它可能随距离、对齐精度、电池温度变化。我们可以引入一个效率函数η(d)其中d是车辆与充电桩线圈的偏移距离。这样匹配时不仅要考虑桩的功率还要考虑停车精度对效率的影响问题变得更复杂。分时电价与成本优化如果引入电网分时电价目标函数可以改为最小化总充电成本。车辆可能会为了避开电价高峰而选择等待调度策略需要调整。车辆移动能耗如果停车场很大车辆从停车位移动到充电桩的能耗不可忽略。目标函数可以加入移动能耗项变成“总能耗移动充电最小化”。预约机制允许车辆预约未来的充电时段这需要引入更复杂的时间窗约束。多目标优化同时考虑总耗时、总能耗、公平性等待时间方差等多个目标可以使用帕累托前沿或加权求和法处理。我们当时选择了分时电价和充电效率随距离衰减两个因素进行拓展。我们修改了问题一的模型目标函数变为最小化总成本Σ_i Σ_j (C_grid(t) * P_j * T_ij * x_ij)其中C_grid(t)是t时刻的电价。同时T_ij的计算考虑了距离相关的效率η(d_ij)。求解这个模型需要知道每辆车可能的开始充电时间这又和动态调度耦合我们采用了一种两阶段启发式算法来近似求解。4.2 灵敏度分析怎么做灵敏度分析是数模论文的“标配”用来检验模型的稳健性。通常做法是改变某个关键参数观察目标函数或最优解的变化。参数选择充电桩功率P_j、车辆到达率λ、电池平均容量C_i、充电效率η、电价差ΔC等。分析方法单因素分析固定其他参数连续改变一个参数如将充电桩功率统一提高10%、20%、30%...运行模型记录总耗时、成本等指标的变化绘制折线图。场景对比设置几种典型场景如工作日早高峰、周末休闲时段、夜间低谷期对比不同调度策略在这些场景下的表现。Python实现通常写一个循环在循环内修改参数调用之前的模型或仿真函数收集结果。def sensitivity_analysis_power(base_power_list, scale_factors): 分析充电桩功率缩放对总时间的影响 results [] for scale in scale_factors: scaled_power [p * scale for p in base_power_list] # 调用问题一的求解函数传入新的功率列表 total_time, _ solve_assignment_model(scaled_power, ...) # 假设有这个函数 results.append((scale, total_time)) return results # 绘制灵敏度分析图 import matplotlib.pyplot as plt scales [0.5, 0.75, 1.0, 1.25, 1.5] times [ ... ] # 通过上述函数得到 plt.plot(scales, times, o-) plt.xlabel(充电桩功率缩放因子) plt.ylabel(系统总充电时间小时) plt.title(充电桩功率对总时间的灵敏度分析) plt.grid(True) plt.show()4.3 获奖论文写作心得模型和代码是基础论文才是最终呈现的载体。一篇好的数模论文逻辑清晰、图文并茂、表达专业。摘要重中之重要用精炼的语言概括问题、方法、模型、算法、主要结果和结论。我们采用“针对问题X我们建立了Y模型采用了Z算法求解得到了A结论并进行了B拓展分析”的结构。摘要里可以出现关键数据和结论。问题重述与分析不要照抄题目要用自己的话梳理问题的背景、条件和目标并分析问题的特点动态、优化、带约束等。模型假设合理且必要的假设是模型的起点。要写清楚并简要说明为什么这样假设如“假设充电效率恒定以简化模型”。符号说明用三线表列出所有主要变量、参数和符号让评委一目了然。模型建立与求解这是核心章节。对应问题一、二、三分小节阐述。每个模型都要有模型定义决策变量、目标函数、约束条件公式要编号、整齐。算法设计你是如何求解这个模型的精确算法如调用求解器还是启发式算法如果是启发式算法如遗传算法、模拟退火用于问题二的滚动优化要详细描述流程可以用流程图。代码与实现细节不必贴全部代码但可以给出关键算法的伪代码或流程图并说明使用的工具Python 3.8, PuLP 2.4, NumPy 1.19等。结果分析与可视化数据给出核心结果数据比如最优匹配方案表、不同策略下的性能指标对比表。图多画图匹配关系网络图、动态仿真甘特图展示车辆占用充电桩的时间线、灵敏度分析折线图/柱状图、不同策略对比图。图要清晰有标注。分析结合数据和图解释现象。例如“从图5可以看出采用SPT策略后平均等待时间比FCFS降低了30%但最长等待时间有所增加体现了其公平性上的不足。”模型评价与推广优点客观评价自己模型的优点如考虑因素全面、求解效率高、结果合理。缺点诚恳指出模型的不足和假设的局限性如未考虑车辆排队拥堵、充电效率模型过于简化等。推广提出模型可以进一步应用的方向如换电站调度、共享单车调度等。参考文献规范引用如果参考了某些算法或模型一定要引用。附录可以放核心代码的片段不要全部、大型数据表等。我们当时在论文中用networkx绘制了匹配关系图用matplotlib的broken_barh绘制了充电桩利用的甘特图这些可视化效果为论文增色不少。记住评委看论文的时间有限直观的图表往往比大段文字更有说服力。5. 实战踩坑与代码优化经验最后分享一些我们当时实际编程和建模中遇到的“坑”以及解决办法这些是你在教科书和简单教程里很难看到的。5.1 数据预处理与异常值处理题目给的数据可能有缺失、异常或需要转换。例如SOC电量百分比可能超过100%或低于0%这显然不合理。我们在读取数据后第一时间进行了清洗和合理性检查def clean_data(df_cars, df_piles): # 检查并处理车辆数据 # 1. SOC范围修正 df_cars[current_soc] df_cars[current_soc].clip(0, 100) df_cars[target_soc] df_cars[target_soc].clip(df_cars[current_soc], 100) # 目标电量不能低于当前电量 # 2. 计算所需能量并处理负值如果目标电量低于当前电量则不需要充电 df_cars[energy_needed] df_cars[capacity] * (df_cars[target_soc] - df_cars[current_soc]) / 100.0 df_cars[energy_needed] df_cars[energy_needed].apply(lambda x: max(x, 0)) # 3. 充电桩功率非负检查 df_piles[power] df_piles[power].apply(lambda x: max(x, 0)) return df_cars, df_piles5.2 整数规划求解的规模与效率问题当车辆和充电桩数量达到几百时0-1整数规划模型的变量数会达到数万m*n直接求解可能非常慢甚至内存不足。我们遇到了这种情况解决方案是添加有效不等式或预处理例如如果车辆i到充电桩j的距离d_ij远大于阈值可以直接预先设定x_ij 0减少变量。使用启发式算法获得初始解先用贪心算法如每次将车分配给当前能使总时间增加最少的空闲桩求一个可行解然后将这个解作为整数规划求解器的初始解可以大大加快求解速度。PuLP本身不直接支持设置初始解但ortools或Gurobi支持。分解或简化模型对于动态问题我们放弃了在每个时间点求解完整的大规模整数规划而是采用前述的滚动优化只对当前等待队列中的少量车辆进行优化规模可控。调整求解器参数PuLP调用CBC求解器时可以设置时间限制、容忍gap等。# 使用PuLP时设置求解时间限制和容忍gap prob.solve(pulp.PULP_CBC_CMD(timeLimit60, gapRel0.05)) # 限制60秒相对gap 5%5.3 动态仿真中的时间精度与事件堆管理在离散事件仿真中时间通常是浮点数。当两个事件时间非常接近时由于浮点数精度问题可能导致事件顺序错乱。我们统一将所有时间转换为整数如以秒为单位来处理。另外频繁使用heapq.heappush和heappop是高效的但要确保Event类的__lt__比较方法正确无误。一个更隐蔽的坑是同时事件的处理。比如多辆车在同一时刻到达或者一辆车结束充电释放桩的同时正好有新车辆到达。我们的处理原则是在同一时刻优先处理“释放资源”的事件如finish再处理“请求资源”的事件如arrive。这样可以避免资源刚释放就被同一时刻到达的事件错过。在代码中可以通过在Event类里增加一个优先级属性或者在__lt__方法中当时间相同时定义事件类型的优先级来实现。5.4 结果的可视化与可解释性不要只输出数字。我们用了以下可视化来让结果更生动匹配图用networkx和matplotlib画出车辆和充电桩的二分图用边的粗细表示充电时间长短。甘特图展示每个充电桩随时间的变化何时被哪辆车占用一目了然。这能清晰对比不同调度策略的差异。性能指标对比柱状图将FCFS、SPT、EDF等策略的平均等待时间、桩利用率等指标放在一起对比。import matplotlib.pyplot as plt import matplotlib.patches as mpatches # 简单的甘特图绘制示例 (数据需从仿真结果中整理) fig, ax plt.subplots(figsize(12, 6)) piles [Pile1, Pile2, Pile3] colors [tab:blue, tab:orange, tab:green] # 假设有一个记录每个桩工作时段的数据结构[(start1, end1, car_id1), ...] schedule { Pile1: [(0, 2.5, CarA), (3.0, 5.5, CarC)], Pile2: [(1.0, 4.0, CarB)], Pile3: [(2.0, 6.0, CarD)], } for idx, pile in enumerate(piles): for start, end, car in schedule[pile]: ax.broken_barh([(start, end-start)], (idx-0.4, 0.8), facecolorscolors[idx]) ax.text((startend)/2, idx, car, hacenter, vacenter, colorwhite) ax.set_yticks(range(len(piles))) ax.set_yticklabels(piles) ax.set_xlabel(Time (hours)) ax.set_title(Charging Pile Schedule (Gantt Chart)) plt.grid(axisx, linestyle--, alpha0.7) plt.tight_layout() plt.show()回顾整个解题过程从最初的茫然到一步步厘清思路、构建模型、编写代码、调试仿真、撰写论文是一个充满挑战但也收获巨大的系统工程。这道“电动汽车无线充电优化匹配”赛题完美地结合了数学建模、算法设计和编程实现。对于后来者我的建议是不要畏惧复杂的背景抓住优化问题的本质决策变量、目标、约束善用Python强大的科学生态PuLP/ortools用于优化SimPy/自定义仿真框架用于动态模拟Matplotlib/Pandas用于分析和可视化最后把你们严谨的思考和精美的结果通过结构清晰的论文呈现出来。希望这篇超详细的复盘能帮助你在未来的数模竞赛或类似项目中少走一些我们曾经走过的弯路。