ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

电动汽车充电调度论文复现:从数学模型到Python工程实现

2026/9/11 20:16:57 拓冰建站 浏览量
电动汽车充电调度论文复现:从数学模型到Python工程实现 这个标题一看就是很多电动汽车方向研究者的入门刚需。我在做智能电网相关课题时也接过类似的项目论文里数学公式写得漂亮但真要把它变成能跑、能复现实验曲线的代码中间隔着大量隐式假设和工程细节。尤其是“考虑不同充电需求”这几个字直接决定了调度模型复杂度也决定了代码不能是“一个贪心算法打天下”。这篇复现笔记我会按自己的实现顺序来写核心思路是把原论文的逻辑还原成可运行的Python工程包括数学模型怎么落代码、数据集怎么构造、约束怎么处理、实验图怎么复现以及我实际踩过的几个坑。适合正在复现类似论文、刚接触电动汽车充电调度、或者想把这套方法迁移到自己研究场景里的朋友。1. 首先理解原论文解决的真实场景为什么“不同充电需求”是关键前提复现论文代码之前最忌讳的就是拿着公式直接翻译。你得先搞清楚这个模型到底在解决什么现场问题。这篇论文的核心场景是一个充电站或一个区域内有大量电动汽车需要充电但配电网容量和充电桩数量有限如果所有车都“插上就满功率充”高峰期变压器会过载电价高时用户也多花钱到了晚上却有一堆车已经充满在占着桩。所以论文提出了“协调充电调度”由调度中心统一安排每辆车在什么时间、以多大功率充电满足充电需求的同时压低负荷峰值或充电费用。“不同充电需求”这个前提是为了让模型更贴近现实。我梳理了一下大多数同类论文会把车辆分成这么几类需求类型典型场景充电紧急程度可调度灵活性应急快充网约车、急救车辆停留时间短很高必须在短时间内补到指定SOC低基本按接入即充常规通勤上下班通勤车辆白天停在公司中等离场前充满即可高可以在工作时间段内灵活分配功率过夜慢充居民区车辆晚上停一整个晚上低次日早上取车时充满很高可以平移到凌晨低价时段如果模型不区分这些需求统一用“最晚离场时间”作为唯一约束那应急车辆可能会被排到后面造成用户体验极差反过来如果所有车都按应急需求处理那调度就退化成无序充电论文也就没有创新点了。所以“考虑不同充电需求”本质上是在模型中引入用户类型这个维度并给每类车辆附加不同的优先级或约束。1.1 协调调度的本质在“满足用户”和“电网安全”之间找解用大白话解释协调调度就是一个“资源分配问题”。资源是每个时间段的充电功率分配对象是每一辆车约束条件是用户的充电需求和电网的容量上限优化目标一般是总费用最低、峰值负荷最小或用户满意度最高。从数学上看这最终落成一个混合整数线性规划MILP问题。车辆接入/离场时间是0-1变量或者边界条件充电功率一般是连续变量而“某辆车在某个时段是否充电”这种判断需要整数变量。对于小规模场景可以直接用求解器求精确解对于大规模场景论文通常会改用启发式算法或分解算法。1.2 复现前必须做好的心理准备复现这类论文要有一个清醒认识原文未必把所有参数都给了。很多时候论文只给目标函数和约束的数学表达不给你每辆车的到达时间分布、电池容量、充电功率上限这些数据。这在你开始写第一行代码前就变成了一个需要“设计实验数据”的任务。所以我的建议是先通读论文的实验部分看它用了什么数据的描述。如果用了真实数据集就直接找对应数据集如果是随机生成的就尽量从图里反推参数范围比如“测试车辆数量为100辆电池容量30~60kWh充电功率3.5~22kW”这种描述已经足够构筑一个可复现的实验环境了。2. 数学模型逐条拆解目标函数、约束条件和参数设计通读完场景接下来就是啃公式了。这一步不能跳过因为代码里的每一个数组索引、每一个约束条件都对应着公式里的一个符号。而且很多论文符号写得比较省略特别是时间索引这里需要自己补齐细节才能写代码。2.1 参数定义部分先把符号系统统一起来这类论文通常存在多个时间尺度一个是“调度周期”T比如24小时一个是“时间间隔”Δt比如15分钟或1小时。我建议在代码里先定义一个全局配置类把所有的参数集中管理避免后期改起来麻烦dataclass class EVChargingConfig: T: int 24 # 调度周期内的时段数 delta_t: float 1.0 # 每个时段的时长单位小时 num_evs: int 100 # 电动汽车数量 grid_capacity: float 250.0 # 变压器或充电站总功率上限单位kW max_charging_power: float 22.0 # 单台充电桩最大功率 min_charging_power: float 0.0 # 最小充电功率通常可为0 # 电池参数范围 battery_capacity_range: tuple (30.0, 60.0) # 单位kWh initial_soc_range: tuple (0.2, 0.8) # 初始荷电状态比率 target_soc: float 0.9 # 离场目标SOC price_profile: list None # 分时电价长度为T这个配置类看起来不复杂但它解决了一个很现实的问题论文里的符号体系跟代码不一定一一对应。你肯定不想在写目标函数时发现还要满代码找某个常量定义在哪个文件里。2.2 目标函数为什么这样构造大多数充电调度论文的目标函数都是单目标或者加权多目标。常见的有这么几类最小化总充电费用min sum(p_i_t * price_t * delta_t)最小化峰值负荷min max(sum(p_i_t))最大化用户满意度max sum(soc_i_departure)或者最小化SOC缺额。很多论文都会做“加权和”例如min cost lambda * peak_penalty mu * unsatisfied_penalty复现的时候要特别注意权重系数的取值。原文如果给了最好如果没给就需要自己去标定。一个简单的做法是跑几次基线比如无序充电得到费用的量级再根据你希望突出哪个目标来定权重确保量级匹配。比如费用是几千元峰值为几百kW那lambda如果取0.1峰值项对目标贡献就太小了等于没有优化峰值。如果原文没有明确写出权重你可能需要在复现说明里标注“此处为复现时自行设定的取值”这在学术复现中是完全合理的但要在文档里写清楚。2.3 约束条件的现实意义论文里的约束看起来多其实可以分三组来理解。第一组单台车的充电功率约束。每个时段内充电功率必须在0到最大功率之间。如果支持有序充电的功率可调这就是连续变量边界如果只支持开/关式充电这就是0-1整数变量。第二组电池SOC状态转移约束。即soc_i_{t1} soc_i_t (p_i_t * delta_t * eta) / battery_capacity_i其中eta是充电效率。这里很多新手会漏掉除以电池容量这一步导致SOC直接飞到天上。第三组电网或充电站总功率约束。即任一时刻所有在充车辆的功率之和不能超过站点的容量上限同时还要考虑变压器或线路的负载上限。还有一个容易忽略的约束如果车辆在某个时段还没有接入或者已经离场充电功率必须强制为0。这个在代码里一般通过接入时间索引和离场时间索引来实现在约束里表现为变量上界乘以一个0/1的可用标志位。3. 代码结构设计与数据准备从论文符号到可运行工程数学公式拆清楚了下面讲代码怎么写。很多复现项目毁就毁在“代码结构混乱一个 Notebook 堆到底”。我自己的经验是即便你只是短期实验也要分模块写这样后面调参数、换算法、出图都会快很多。3.1 总体模块划分我建议至少分成这几个文件config.py全局配置和默认参数data_generator.py随机生成车辆到达、离场、初始SOC、电池容量等数据model.py构建MILP模型包含目标函数和约束solver.py调用求解器并输出结果到结构化数据比如DataFrameanalysis.py计算指标、出图run_experiment.py主流程串联以上模块。这种划分的好处很明显如果你想换一个求解器或启发式算法只需要替换solver.py里的部分函数如果你想换数据集只需要换data_generator.py如果你想用真实电网负荷曲线也只需要改配置里读数据的逻辑。3.2 数据生成的细节设计论文数据没公开时自己生成随机数据也要讲究合理性不能随便乱取。下面是我用过的生成逻辑车辆的到达时间用泊松过程生成一天内的到达时刻考虑到早晚高峰会形成聚集可以设置分段到达率。比如早上7~9点到达率较高下午17~19点到达率较高其他时间相对稀疏。初始SOC取0.2到0.8之间的均匀分布或者截断正态分布。可以设定早上到的车初始SOC偏低过夜消耗大下午到的车初始SOC相对高一些。电池容量按30kWh到60kWh均匀分布对应市面上主流的纯电车型。充电功率上限家用慢充桩一般7kW公共快充桩一般60kW。数学模型里可以统一设为某个最大功率实现时再在约束里限制。但注意如果你的论文场景是小区慢充那最大功率就是7kW左右而不是22kW。最晚离场时间通勤车一般是当天18:00过夜车是次日08:00应急车辆可能是接入后1~2小时。分时电价可以采用峰谷电价比如峰时1.2元/kWh、平时0.8元/kWh、谷时0.4元/kWh。生成后最好做一次可视化比如画一下“每个时段的在网车辆数”和“总充电需求”分布。如果数据生成不合理比如过夜车辆数量过多可能导致夜间所有车辆同时充电即便调度也压不住峰值这时候模型就自然不可行。可视化能帮你提前发现这种问题。3.3 关键数据结构设计在Python里我习惯用Pandas DataFrame来存车辆基本信息和每个时段的充电计划结果。车辆信息表长这样ev_id arrival_time departure_time battery_capacity initial_soc max_power type priority 0 7.0 18.0 50.0 0.65 7.0 通勤 1 1 8.0 9.5 40.0 0.40 60.0 应急 3 ...充电计划结果表长这样ev_id time_slot power 0 7 6.5 0 8 7.0 ...这种“长表”格式对于后续聚合和画图非常方便。如果用numpy二维数组[num_evs, T]来存也不是不行但最后做数据分析时还是要转成DataFrame不如一开始就直接用。4. 核心调度算法实现分群、排序与冲突消解这里进入真正的算法实现环节。根据原论文思路“不同充电需求”主要通过两步落进模型第一步是对车辆进行分群/分级第二步是在调度求解时给不同车辆分配不同优先级或约束。4.1 充电需求分类简单而有效的手写逻辑如果论文没有用复杂的聚类算法通常是根据停留时间、充电紧急程度、SOC需求三个维度做规则分类。我复现时直接写了阈值判断def classify_ev(row): # row包含 arrival_time, departure_time, initial_soc, target_soc stay_hours row[departure_time] - row[arrival_time] soc_gap row[target_soc] - row[initial_soc] required_energy soc_gap * row[battery_capacity] max_achievable_energy row[max_power] * stay_hours * 0.9 if stay_hours 1.5 or max_achievable_energy required_energy * 0.8: return urgent # 应急 elif stay_hours 6: return overnight # 过夜慢充 else: return commute # 常规通勤这里的关键是“可调度性”判断如果在最大功率下连续充都完不成目标那这辆车必须优先充电调度必须优先满足它不能让它参与优化排队。这种规则分类看着简单但它已经体现了论文“不同充电需求”的核心逻辑。需要注意的是分类之后不要只用类名来标记还要给每个类分配一个数值优先级。应急车辆设为最高优先级比如3通勤车辆其次比如2过夜车辆最低比如1。后面的求解器里这个优先级会用来构造加权目标函数或者作为硬性约束的判定依据。4.2 用线性规划求解器实现协调调度以PuLP为例我们可以很清晰地构建出MILP模型。下面是一个简化的调度实现完整版本我在自己的项目里扩展了更多约束import pulp def build_scheduling_model(ev_info, price, grid_capacity, T, delta_t): prob pulp.LpProblem(EV_Coordination_Scheduling, pulp.LpMinimize) # 决策变量第i辆车在第t时段的充电功率 power {} for i in ev_info.index: for t in range(T): power[(i, t)] pulp.LpVariable( fp_{i}_{t}, lowBound0, upBoundev_info.loc[i, max_power] ) # 目标函数总充电费用 峰值惩罚 cost_expr pulp.lpSum( power[(i, t)] * price[t] * delta_t for i in ev_info.index for t in range(T) ) # 峰值可以用一个辅助变量近似 peak pulp.LpVariable(peak, lowBound0) for t in range(T): prob pulp.lpSum(power[(i, t)] for i in ev_info.index) peak prob cost_expr 0.05 * peak # 权重需根据实验标定 # 约束1可用时间窗口内才能充电 for i in ev_info.index: a int(ev_info.loc[i, arrival_time] / delta_t) d int(ev_info.loc[i, departure_time] / delta_t) for t in range(T): if t a or t d: prob power[(i, t)] 0 # 约束2SOC需求必须满足 for i in ev_info.index: prob pulp.lpSum( power[(i, t)] * delta_t * 0.9 for t in range(T) ) (ev_info.loc[i, target_soc] - ev_info.loc[i, initial_soc]) * ev_info.loc[i, battery_capacity] # 约束3任意时刻的总功率不能超过站点容量 for t in range(T): prob pulp.lpSum(power[(i, t)] for i in ev_info.index) grid_capacity # 约束4优先保障应急车辆 urgent_ids ev_info[ev_info[type] urgent].index for t in range(T): prob pulp.lpSum(power[(i, t)] for i in urgent_ids) 0 # 占位 # 实际项目中可以给应急车辆单独设置最小充电功率约束 return prob, power这段代码里峰值惩罚项我用的是一个辅助变量peak。这在实际求解中非常常见比直接定义max函数更优雅。约束2用“充电量 需求电量”而不是逐时段计算SOC是因为线性规划里逐时段SOC计算会让模型多出很多变量性能变差。4.3 求解与结果落地构建好模型之后直接调用prob.solve()就行。默认是CBC求解器对小规模问题已经够用。求解完成后要把决策变量提取回DataFrameresult_records [] for (i, t), var in power.items(): if var.varValue and var.varValue 0: result_records.append({ ev_id: i, time_slot: t, power: var.varValue }) result_df pd.DataFrame(result_records)提取这一步还有一个细节如果是整数变量要把结果取整如果是连续变量最好保留两位小数避免后面画图时出现密集的毛刺。另外var.varValue在求解失败时是None所以要做好判空。4.4 程序执行流程串联整个主流程其实很短加载配置生成或读取车辆数据对车辆进行分类打上优先级标签调用build_scheduling_model构建模型求解提取结果运行后验检查后面专门讲画图计算指标。跑完后你会发现程序的核心逻辑不过一两百行。但为了得到这个简单的核心前面数学建模和数据准备花掉了绝大多数时间这是完全正常的。5. 实验复现与结果验证关键指标、对比图表和一致性检查模型跑通之后接下来要回答的问题是结果对不对原论文的图能不能复现出来这里不只是对数值还要对趋势、对相对关系。5.1 必须构建的基准对比没有对比调度结果就没有说服力。我在复现时至少对比了三种策略无序充电车辆接入后立刻以最大功率充电直到充满或离场。定时充电所有车统一在电价低谷时段启动充电通常是夜间23:00到次日7:00。论文协调充电本文模型的结果。通过对比这三种策略可以看出协调充电在费用、峰值、充电完成率三个指标上的差异。这也是论文里最常见的三个图费用柱状图、负荷曲线图、SOC分布图。一个有效的复现实验要做到论文中的结论性趋势在你的代码里重现例如相比无序充电协调充电使总费用降低10%~20%负荷峰值降低15%~30%并且没有车辆完不成充电。5.2 指标计算要统一口径指标这块有太多复现者踩过坑我自己也踩过。我建议一开始就明确规定总费用 所有车辆在各时段结算的电费之和单位元峰值负荷 所有时段中总充电功率的最大值单位kW充电完成率 到达目标SOC的车辆数 / 总车辆数单位百分比用户平均满意度 离场时实际SOC与目标SOC比值的平均值。其中充电完成率这个指标如果模型中设置了“SOC需求必须满足”的硬约束那理论上应该始终是100%。但如果求解器告诉你不可行你就得松绑约束或者提高站点容量。所以我还建议在模型里加入一个“未满足电量”的软性变量方便观察哪些车被牺牲了。5.3 画图时的细节复现论文图表最常见的坑是横纵轴不对齐。假设论文的负荷曲线横轴是0~24小时纵轴是kW你的调度时间步长是15分钟那画图前要先按小时聚合不能直接画96个点再去跟论文对比那样看起来对不上。还有就是“接入即充”的无序充电曲线峰值往往出现得非常早7点到8点之间可能就满了。如果你的数据生成不合理比如早上车辆过于集中到达那峰值会直接顶到站点容量上限这时候协调充电的效果反而体现不出来因为你根本没有足够的冗余。所以生成数据后一定要多检查几遍。6. 复现过程中最容易踩的坑完整排查链路与修复建议这一段我写得会比较细因为这些问题我在实际做的时候一个都没躲过而且是反复出现。如果你正卡在某个报错或结果异常上可以直接按下面的链路来排查。6.1 模型不可行先看约束条件哪个太紧模型不可行是最让人头大的错误因为求解器只告诉你“infeasible”不告诉你是哪个约束出了问题。我第一次做时候直接愣住了后来终于总结出一套排查链路。第一步先去掉SOC需求约束只保留功率上下限和站点容量约束。如果可行说明问题出在总需求大于站点供给能力。这时候你要么降低车辆数量要么提高站点容量要么把部分车辆的SOC需求降低。第二步加上一天内总电量约束不加逐时段约束。如果这时候还不可行说明每条车“总充电量需求”本身就超过了可行范围可能是初始SOC设置太低或者目标SOC过高导致需要充电的能量超过电池容量差与充电效率的乘积。第三步检查时间窗口。这是最隐蔽的坑。因为论文里的到达时间和离场时间往往用小数表示比如7:30是7.5而你做了int()转换后可能把可用时间窗口算错了。例如7:30到达、8:00离场如果delta_t1那么可用时段是[7, 8)其实只有一个小时如果你处理成[7, 8]两个时段就给了它两个小时的充电量上限。这在聚合约束里会造成模型“虚胖”可行但在逐时段调度里可能出问题。6.2 SOC越界或未达目标先检查效率系数和电池容量归一化这个问题主要出在SOC转电量的换算上。模型约束通常写成“充电量 需求电量”但需求电量是目标SOC - 初始SOC× 电池容量而充电量是功率 × 时长 × 效率。一个常见错误是忘了乘效率或者忘了除以电池容量。比如有些论文里的状态转移写的是soc_{t1} soc_t p_t * delta_t * eta / battery_capacity但你在约束里却写成了sum(p_t * delta_t) (target_soc - init_soc) * battery_capacity如果把delta_t不小心写成了总调度周期T而不是单位小时时长或者效率系数写错就会导致模型认为“充一度电就能满足目标”最后结果自然不达标。6.3 求解时间过长问题规模太大时怎么降维用MILP求解100辆车、96个时段变量数就接近一万CBC可能要跑很久。如果论文里是几百辆车精确求解基本不现实。我建议的路线是先试试把时间步长从15分钟放大到1小时能大幅降低决策变量数量用“聚合充电”策略简化模型先不管早上8点这一辆跟8点15那辆的区别如果还想更快可以用启发式算法替代精确求解比如遗传算法或粒子群算法用来跟MILP结果做交叉验证。但是要记住使用启发式算法的时候必须把MILP的小规模精确解作为参照评估启发式算法的解离最优解差距有多大否则论文审稿人一定会问你这个差距的问题。6.4 结果重复率低随机种子和固定随机流生成随机数据时一定要固定随机种子。我见过太多人跑了一次结果不错第二次数据不一样结果又崩了然后以为算法有bug其实只是随机波动。建议在data_generator.py里显式写上np.random.seed(42)如果要用到多组随机实验求平均再在config.py里增加一个random_seed字段每次实验传入不同种子最后统计均值和方差。7. 进一步扩展把复现代码改造成自己的研究工具复现本身不是终点。如果你做课题、发论文或者想做一套实用的充电调度策略这批代码其实是一个不错的底座。我根据自己的经验列几个扩展方向。7.1 从单站扩展到多站场景原论文可能只考虑一个充电站但实际城市里有多个站点共享同一配电网。这时候模型里需要在总功率约束那一行进行跨站聚合。代码改动其实不大就是把grid_capacity扩展到每个站点的容量向量并把站点ID作为决策变量索引的第三维。不过多站延展后计算复杂度会显著上升。我的建议是先用低精度时间步长做可行性和效果验证再逐步细化。7.2 加入实时滚动优化很多论文是离线调度也就是已知一天内所有车辆的信息后一次性求解。实际更常用的是滚动时域优化每隔15分钟或1小时重新求解一次只决策未来几个小时的充电计划滚动推进。这会对代码结构有较大改动因为你不再需要一次生成24小时的数据而是每轮循环读取当前状态、更新剩余车辆和剩余需求然后重新调用求解器。这个方向做出来论文的创新点也会更足。7.3 与深度学习预测模块整合现在比较热的方向是把充电需求预测、电价预测或用户行为预测模型接到调度模型前面。比如用LSTM或Transformer预测未来几小时的总充电需求然后把预测值作为约束条件输入调度模型。这个你就可以参考网络热词里经常出现的“深度学习代码复现”“多模态模型代码复现”那些项目里对模型封装和训练的技巧形成一套“预测调度”的端到端流程。我的建议是把预测模块和调度模块之间用清晰的接口隔开预测模块输出为一个DataFrame调度模块只认DataFrame不直接依赖预测模型的内部实现。这样你换预测模型的时候调度代码一行都不用动。7.4 加入用户行为随机性最后一个小扩展也是论文里比较常见的给用户行为加入随机性比如到达时间提前或延后、实际充电需求变化。这个在你的代码里体现为在求解完成后随机改一些参数观察原调度方案的鲁棒性。我在做类似测试时发现应急车辆占比越高调度方案的鲁棒性越差因为少数的到达波动就可能导致峰值越界。实际运行中要预留5%~10%的容量余量这也是工程上非常常见的做法。写在最后复现这篇论文的过程中我最大的体会是论文代码复现的难点不在代码本身而在“把作者的隐式假设补全”。作者可能默认了你懂电网的负荷特性默认了你理解SOC状态转移的物理含义默认你知道目标函数里那个权重该怎么调。这些隐含信息只能靠一遍遍读原文、一遍遍对照实验数据去逼近。如果你现在正准备入手这个方向我建议不要一上来就追求完美复现原论文的每一张图。先跑通一个小规模的简化模型哪怕是20辆车、24小时、一个求解器把整个流程走通再逐步增加细节。有了完整流程后面的扩展才有抓手。这是我自己绕了不少远路才总结出来的经验希望能让你少走几步。