首页 / 资讯中心 / 文章详情

用Python复现论文:风电光伏与电动汽车协同调度模型

用Python复现论文:风电光伏与电动汽车协同调度模型 ★ FEATURED ARTICLE
这篇论文——《可再生能源发电与电动汽车的协同调度策略研究》——从题目看是把电力系统里两个“不太好惹”的对象放在一个框架里做联合优化一边是风电、光伏这类出力完全看天吃饭的电源另一边是充电行为高度分散又时间灵活的电动汽车。有经验的调度从业者看到这个组合应该立刻能嗅到价值风电夜间大发、光伏午间过剩而电动汽车恰恰可以在这些时段充当“海绵”负荷把弃风弃光时段变成充电时段。但真正动手复现这篇论文时你会发现难点并不在“联合优化”这个概念而在于如何把论文里几页公式转换成一套能求解、能出图、能和你预想结果对得上的Python代码。这次我把论文中的核心调度模型完整复现了一遍从数学建模、约束推导、求解器配置到结果可视化整条链路都跑通了。这篇文章不打算复述论文原文而是以一个做过复现的人的口吻把整个工程拆解过程、建模细节、代码架构和踩过的坑都整理出来。适合正在做电力系统调度优化方向课题的研究生、准备复现论文做实验对比的开发者以及对微电网、虚拟电厂协同控制感兴趣的算法从业者参考。1. 研究背景与问题拆解1.1 为什么“风电光伏电动汽车”需要协同调度风力发电和光伏发电的特点做电力系统方向的人再熟悉不过——出力曲线由天气说了算。风电往往夜间大、白天小光伏则是中午峰值、早晚爬坡这些出力特征跟常规负荷曲线经常是错位的。电网调度面对常规火电机组时可以按日前预测安排出力计划可一旦系统中波动性电源的渗透率上来前一晚做好的计划第二天早上就可能完全失效。这时候要么开更多备用机组扛着要么直接弃风弃光换取系统频率稳定无论哪条路经济性都很差。电动汽车的规模化接入恰好为这个问题提供了新的解决维度。和空调、热水器这类刚性负荷不同电动汽车的约束只是“在离开之前把电量充满”至于在哪个时间段充、以多大功率充其实是有相当大的主动调节空间的。如果把大量电动汽车看作一个分散式储能集群风电大发时让它们集中充电光伏过剩时让它们吸收余电电网高峰时甚至可以短暂反向放电原本无解的“反调峰”问题就多了一条现实可行的出路。“协同调度”这三个字的价值就在这里——不是简单地把可再生能源和电动汽车摆在同一个系统里而是把可再生能源出力的不可控性、常规机组的运行约束、电动汽车集群的灵活性放进同一个优化框架内做联合决策。这样得到的调度结果在理论上一定是帕累托意义上更优的方案而不是两者各自为政的简单叠加。1.2 硕士论文复现的正确打开方式复现一篇硕士论文的调度策略最忌讳的是一上来就看代码、抄代码、跑代码。如果对论文的数学建模框架没有清晰认知跑通了也不知道在跑什么结果对不上更无处下手。我拿到这篇论文后先做了三件事确认建模框架是单层优化还是双层博弈目标函数是成本最小化、碳排放最小化还是多目标加权确认变量类型决策变量是纯连续变量如机组出力还是混合了整数变量如电动汽车充放电切换的0-1状态、机组启停变量确认求解思路论文是用数学规划的求解器直接求解还是用了遗传算法、粒子群这类启发式方法这两类方法在代码结构和结果可复现性上差异巨大。本文复现的论文模型属于典型的单层混合整数线性规划目标函数在系统总运行成本最小化的基础上外加弃风弃光惩罚项约束条件包括功率平衡、常规机组出力上下限与爬坡约束、电动汽车电池SOC转移方程和充放电功率限制。这种模型的好处是结构规整求解器能保证全局最优复现时只需要把公式逐条翻译成代码即可不需要像启发式算法那样反复调参试算。2. 数学模型搭建从物理场景到优化表达式2.1 目标函数设计成本最小化到底在优化什么论文的目标函数我按复现的需要拆解成了四个部分。首先是常规机组的发电成本通常用二次函数表示C_g(P) a_g · P² b_g · P c_g其中a、b、c是机组的成本系数。注意这个二次函数它是后面所有“工程麻烦”的源头——直接丢给混合整数线性规划求解器会变成二次规划虽然也能解但求解稳定性、速度都会受影响商用求解器对大模型的MIQP处理也没有纯MILP那么顺手。我在复现时采用了分段线性化方案把每台机组的出力范围平均切成若干段我用10段每段的线性斜率代表该区间的边际成本引入一个额外的连续变量和一组凸组合约束来逼近原二次曲线。实测下来分段数到10之后精度已经足够结果跟原文对照没有可感知的误差。第二部分是弃风弃光惩罚成本F_re λ_re · Σ_t ( P_re_pred(t) - P_re_used(t) )λ_re是惩罚系数P_re_pred是预测的可再生出力P_re_used是实际消纳量。这一项的物理含义是清洁能源已经预测出来却没用系统需要承担惩罚。关键是这个系数怎么取——如果设得低优化器会为了省一点机组成本而大量放弃清洁能源结果图上出现一片弃风区跟论文想表达的理念相悖如果设得过高又可能让系统为了消纳可再生能源而让常规机组频繁大幅调峰导致成本失真。我最终取的是常规机组单位发电成本的1.21.5倍效果跟论文结果曲线拟合得最好这个经验可以给复现类似论文的同学直接参考。第三项是电动汽车的充放电成本F_ev Σ_t ( c_ch · P_ch(t) - c_dis · P_dis(t) )这里有个细节容易被忽略放电项前面是负号相当于V2G模式下电动汽车向电网放电获得的收益。如果没有这项优化器几乎永远不会选择让电动汽车放电因为所有放电行为都有损电池寿命的隐性成本。论文在目标函数里显式写这一项本质上是给“电池损耗”标了价格复现时不能省。2.2 核心约束条件电动汽车与可再生能源的耦合点约束条件是调度建模的真正难点。我按约束类别逐一说明。系统功率平衡约束是最顶层的物理骨架它把所有对象耦合在一起Σ_g P_g(t) P_wind(t) P_pv(t) Σ_ev P_dis(t) P_load(t) Σ_ev P_ch(t)左边是电源侧右边是负荷侧。每条时刻都必须满足这是等式约束求解器在对可行域的搜索中会反复校验它。在这里电动汽车既是负荷充电时又是电源放电时这种“双重身份”在代码里是通过同一个变量在不同时段的正负号来体现的。常规机组约束包括出力上下限和爬坡速率限制。爬坡约束是相邻时段的变化幅度限制-P_ramp(g) ≤ P_g(t) - P_g(t-1) ≤ P_ramp(g)特别提醒初始时段的爬坡需要单独定义论文里如果没写清楚通常是拿当前时刻出力减去初始出力而不是减去第0时刻的变量。这个坑导致前后版本结果差异巨大排查过很久才发现是边界条件理解错了。电动汽车约束是本文复现的重头戏。为了兼顾精度和可解性我没有把每辆车单独建模那样动辄上千辆车的模型会让求解器直接爆炸而是按车辆行为特征分了三大类——快充需求型、常规通勤型、V2G可调度型每类车作为一个聚合体参与调度。这个简化完全符合论文思路每个聚合体的参数是同类车辆的平均值。电动汽车的电池SOC状态转移方程是整个模型中最容易写错的地方SOC_ev(t1) SOC_ev(t) η_ch · P_ch(t)·Δt / E_ev - P_dis(t)·Δt / (η_dis · E_ev)这里面有两个极其隐蔽的坑单位换算如果功率单位是kW、电池容量是kWh、时间步长是1h那么SOC增量等于充放电功率直接除以容量。但如果时间步长是15分钟增量就要乘以0.25。很多复现结果和论文图表对不上的案件最后都“死”在这个系数上。充放电同时性问题理论上应该加一个0-1整数变量保证同一辆车不会同时充放电但聚合模型下如果不加这个限制求解器可能会在同一时段既充又放产生“幽灵能量”。我最初的代码就出现过这种问题充放电两条功率曲线在图上完全对称一加约束才消失。耦合约束是论文结构里最关键的地方。由于可再生能源和电动汽车的协同在功率平衡里可再生能源出力是作为“负的负荷”优先压到净负荷上的常规机组和电动汽车共同响应这个净负荷。同时电动汽车的充电功率上限会动态调整——风电大发时段调度系统通过成本项引导更多车辆充电。这个动态关系不是显式写进某一条约束里的而是目标函数中充电成本项与可再生能源惩罚成本项之间博弈的结果这也是“协同”二字的本质所在。3. Python代码实现思路从建模到跑通的工程拆解3.1 工具选型为什么用优化建模框架而不是自己怼矩阵把数学规划模型转成可求解代码有两条路线。一条是自己用数组和SciPy手写约束矩阵再调用底层求解器接口。这种做法的好处是完全可控坏处是模型一旦改动比如换一种电动汽车分类方式或者加一条新的约束矩阵索引可能全乱。我在工程上更推荐用优化建模库的方式用Pyomo这类建模语言来声明变量、目标函数和约束然后调用背后的求解器。好处是代码和数学公式几乎一一对应后期修改模型时清晰得多。如果你的环境里已经装好Pyomo实际求解流程大致是这样先用一个自己的模型类封装所有数据负荷、风电、光伏、电动汽车参数在模型内部声明所有变量构造目标函数和约束最后创建求解器实例边带求解。初次跑通一个48时段、3台常规机组、3类电动汽车聚合体的模型求解时间通常在几秒到几十秒级别。这个速度足以支撑做完整天的日前调度仿真。3.2 模块架构设计数据、模型、求解、绘图四分离我复现这份论文代码时刻意采用了四层结构这样后续调整参数或扩展论文对比方案时不必动主逻辑。data目录存放风电、光伏、负荷的时序数据以及机组参数表、电动汽车参数表。数据统一用CSV接口读取这样更换数据集时只需替换文件。model目录核心建模文件包含一个返回完整Pyomo模型的函数。函数接受各类参数作为输入在内部声明变量、目标函数、约束并返回模型对象。这里有个工程技巧把“模型构建”和“模型求解”完全分离方便后期对模型做单元测试——比如去掉电动汽车约束跑一个纯机会模型对比结果验证电动汽车在协同中到底贡献了多少。solver目录负责创建求解器实例、调用求解、从结果中提取各变量的调度曲线。提取结果这一步容易忽略因为Pyomo的变量在求解后是一个索引化的对象如果不主动转成DataFrame格式后期画图会非常痛苦。visual目录所有画图的函数输出内容包括风电消纳情况对比图、机组出力组成图、电动汽车充放电功率曲线图以及SOC变化图。这种模块拆分对有论文复现需求的同学特别适用。你大概率会反复修改模型——比如今天加一个碳排放约束明天把电动汽车改成V2G模式如果没有四层分离一个小改动就可能让代码失控。3.3 关键代码片段解析下面是模型构建部分最核心的代码结构省略了完整实现只展示骨架关键逻辑都已经注释import pyomo.environ as pyo def build_model(data): model pyo.ConcreteModel() model.T pyo.Set(initializedata.time_steps) model.G pyo.Set(initializedata.thermal_units) model.EV pyo.Set(initializedata.ev_groups) # 决策变量 model.p_g pyo.Var(model.G, model.T, withinpyo.NonNegativeReals) model.p_ch pyo.Var(model.EV, model.T, withinpyo.NonNegativeReals) model.p_dis pyo.Var(model.EV, model.T, withinpyo.NonNegativeReals) model.p_re_used pyo.Var(model.T, withinpyo.NonNegativeReals, bounds(0, data.renewable_pred.max())) model.soc pyo.Var(model.EV, model.T, bounds(0.2, 0.95)) # 目标函数发电成本 弃风弃光惩罚 电动汽车充放电成本 def obj_rule(model): gen_cost sum(data.thermal_cost[g][0] * model.p_g[g, t] ** 2 data.thermal_cost[g][1] * model.p_g[g, t] data.thermal_cost[g][2] for g in model.G for t in model.T) curtail_cost data.penalty_re * sum( data.renewable_pred[t] - model.p_re_used[t] for t in model.T) ev_cost sum(data.cost_ch[ev] * model.p_ch[ev, t] - data.cost_dis[ev] * model.p_dis[ev, t] for ev in model.EV for t in model.T) return gen_cost curtail_cost ev_cost model.obj pyo.Objective(ruleobj_rule, sensepyo.minimize) # 约束1功率平衡 def power_balance_rule(model, t): return (sum(model.p_g[g, t] for g in model.G) model.p_re_used[t] sum(model.p_dis[ev, t] for ev in model.EV)) \ data.load[t] sum(model.p_ch[ev, t] for ev in model.EV) model.power_balance pyo.Constraint(model.T, rulepower_balance_rule) # 约束2电动汽车SOC转移方程 def soc_rule(model, ev, t): if t 0: return model.soc[ev, t] data.ev_initial_soc[ev] return model.soc[ev, t] model.soc[ev, t-1] \ data.eta_ch * model.p_ch[ev, t] / data.ev_capacity[ev] - \ model.p_dis[ev, t] / (data.eta_dis * data.ev_capacity[ev]) model.soc_transition pyo.Constraint(model.EV, model.T, rulesoc_rule) return model这段代码有几个点值得展开。功率平衡那个函数可再生能源出力用的是p_re_used变量而不是预测值这样模型才能在“弃掉一点可再生能源”和“多开一点机组成本”之间做权衡。SOC转移方程里的时间步长问题因为我在数据里已经确认所有时间序列都是按1小时采样所以代码里没有额外乘系数如果换成15分钟数据这里一定记得乘0.25否则结果差到离谱。目标函数里我保留了发电成本中的二次项是因为我在完整实现中用了分段线性化二次项被替换为分段变量的线性组合。上面这段骨架代码为了可读性保留了二次形式实际完整代码里是用分段线性化处理过的求解速度和数值稳定性都会好很多。完整代码里对二次成本的线性化处理是这样做的把每台机组的出力区间切成K段对每段引入一个额外连续变量表示“该段内被使用的出力份额”然后目标函数中对每个分段变量乘以对应的边际成本斜率再加一个分段定义变量总和等于总出力的约束。这样原来的二次问题就被线性化了。3.3 完整代码中值得留意的数据处理细节数据准备阶段还有一个容易掉链子的点论文图表中展示的风电出力是“已经被调度系统消纳后的实际值”还是“原始预测值”两种含义差很大。如果直接拿原始预测数据跑模型会得到弃风、弃光结果但如果不小心把论文图表上的“已消纳出力”当成预测值输入那结果里反而会得出“完全不弃风”的假象因为预测本来就被模型处理过了。复现论文时我建议先从文中对图表的说明判断清楚图注写的是“风电出力预测曲线”还是“风电调度结果曲线”这两种图的横纵坐标一样但含义完全不同。我这次用的数据是论文场景生成的模拟数据风电与光伏曲线各自覆盖了24小时和48小时两种时间跨度版本对比效果时都能用。4. 核心参数与仿真场景设计4.1 仿真场景设置用数据还原论文的“实验环境”复现效果好不好很大程度取决于参数设置是否贴近论文原文。我把这次用到的核心参数列成一个表供参考参数项数值说明调度周期24小时日前调度时间步长1小时常规机组数3台容量分别为200MW、150MW、100MW成本系数a/b/c各机组不同二次成本函数系数风电装机120MW峰值出力约105MW光伏装机80MW峰值出力约70MW基础负荷峰值约300MW典型日负荷曲线电动汽车聚合体数3类快充型、通勤型、V2G型单类EV数量500~1000辆为控制求解规模按聚合体建模EV电池容量40~80kWh按车型分别设置充放电效率0.95 / 0.95充放电对称弃风弃光惩罚系数600元/MWh常规机组边际成本的1.2~1.5倍场景配置的关键是“让问题处于一个既不过于简单、又不至于无法求解的规模”。模型规模太大求解时间会呈指数级上升规模太小结果又缺乏说服力。我最终选择了3台常规机组加3类电动汽车聚合体。这里面有个非常现实的考虑论文原文的模型如果包含几十台机组和上千辆独立电动车复现成本和难度会成倍上升。工程上合理的做法是先复现“核心简化版”比如我这版把发电侧缩到3台机组、把车辆聚合成3类确认结果趋势和论文一致后再按需扩展机组数量和车辆类型。这样即使最终无法完全重现论文的数值至少可以复现论文的方法论和核心结论。4.2 关键参数调整的几轮经验第一轮直接跑出结果后我对着图发现一个问题电动汽车充放电功率曲线在时间轴上的峰谷位置和预期有偏差。查了一圈发现是给V2G型车辆设置的放电收益系数偏小不足以激励它们在晚高峰放电。第二轮把放电收益从0.4元/kWh提高到0.6元/kWh后曲线马上出现了预期的放电窗口。第二轮的教训是爬坡约束设置得过紧。当时为了模拟常规机组的真实调节能力我把爬坡速率设成了额定容量的10%结果风电出力大幅波动时常规机组跟不上只能大面积弃风。后来把爬坡率放宽到15%消纳率明显提升这也符合真实系统中通过机组组合优化来缓解爬坡压力的思路。第三轮是惩罚系数λ_re的调参这一轮最折腾。最开始我从论文字面上理解“高惩罚”应该设一个大数值直接设成了10000元/MWh结果模型为了不弃风让常规机组出力压到极低水平甚至出现机组低于最小技术出力的情况被约束硬挡下来。后来反复试发现λ_re在当前成本结构下取500~700之间最合适。这个区间能保证“优先消纳清洁能源”的导向又不至于牺牲太多经济性曲线平滑度也有改善。所以如果你们复现的论文对惩罚系数没有明确数值建议用“常规机组边际成本×1.2~1.5”作为起点再逐步调整。5. 结果分析与可视化从数据到论文级图表5.1 调度结果图怎么看、怎么解读复现跑通后第一张要出的图就是典型的“电力电量平衡图”——横轴是24小时纵轴是功率下层堆叠常规机组三种颜色的出力上层叠加风电、光伏的消纳功率和电动汽车充电功率。这张图能一眼看出几条关键信息可再生能源出力是否在午间和夜间都被有效消纳如果图上可再生能源部分出现明显的顶部截断说明有弃风弃光。常规机组是否出现了预期中的“调节行为”协同调度让风电大发时常规机组出力下调光伏大发时同理形成明显的反调峰趋势。电动汽车充放电时段分布是否合理充电集中在夜间和午间放电出现在晚高峰。我在图上叠加了“无协同无序充电”基准策略的曲线作为对照组。两组曲线放在同一张图里对比效果非常直观这也是复现论文最重要的说服力来源。5.2 对比方案设计如何证明协同调度的价值论文的核心结论本质上是一个对比结论协同调度优于无序充电。为了证明这一点我设计了三个场景场景A基准场景电动汽车无序充电一到家就按额定功率充到满。场景B协同调度但有电动汽车参与充电调度不允许V2G放电。场景C完整版协同调度允许V2G放电。跑完三个场景后我把关键指标汇总成一张表系统总运行成本、可再生能源消纳率、常规机组平均出力率、电动汽车充电费用。结果和前两者相比场景C的系统总成本下降了约11%可再生能源消纳率从84%提升到96%常规机组的调峰压力也明显减小。这就是论文最核心的“卖点”——数字一目了然不需要多余的文字解释。这里要提醒一个做对比实验的通用坑三个场景必须使用完全相同的输入数据和参数只改变模型设置。我一开始为了对比方便给不同场景配了不同的惩罚系数结果得出了完全错误的结论——不是协同调度的贡献而是参数差异导致的假象。后来强制规定除实验变量外所有参数保持一致才得到可信的对比结果。5.3 可视化呈现的小技巧论文级图表的细节很影响观感总结三个经验堆叠面积图要选择半透明颜色避免后叠加的数据被遮住。纵轴单位统一风电、光伏、负荷、电动汽车功率都用MWSOC单独画在次要纵轴。图例顺序保持和图层顺序一致方便阅读。我用的绘图库是Python生态里最主流的那个绘图库Matplotlib配合Pandas直接完成数据整理。不要小看图形细节论文审阅人扫一眼图表就能判断工作是否严谨所以该花的时间不能省。6. 常见问题与排查技巧实录6.1 求解器相关模型规模一大就“算不出来”复现过程中最容易撞上的问题是明明模型看着不大求解器却长时间不收敛。我遇到过一例是模型的整数变量用于防止电动车在同一时段既充又放的0-1变量膨大之后求解器直接卡死。解决思路有两个方向降低时间分辨率从1小时改成2小时瞬间减少一半变量。改为聚合模型把同类型电动车合并成一个大充电功率变量而不是逐车辆建模。我从一开始就选择了聚合建模很大程度上就是预判到了这个坑。另外如果你用的是开源求解器可以试试调整求解器的相对间隙MIP Gap容差从默认的1%放宽到2%或3%速度提升显著。对论文复现场景来说MIP Gap控制在2%以内完全可接受最优性偏差对最终结论几乎没有影响。6.2 建模与数据问题结果对不上论文时按顺序排查复现结果和论文图表对不上时不要慌按这个顺序排查参数检查优先查时间步长、功率/容量单位换算、惩罚系数大小。这是最常出错的三个地方。数据对齐风电光伏数据的索引是否与负荷数据完全对应是否有错位。老实说我至少遇到两次由于Pandas索引重排导致的错位问题表现形式就是调度结果出现莫名其妙的周期性波动。SOC边界检查SOC约束的上下限是否合理。上限设得太低比如0.9会导致V2G放电后无法在离开前充满下限设得太高又会限制放电深度让V2G的效果大打折扣。初始状态第一天初始SOC、初始机组出力是否和论文一致。很多复现结果的偏差不是模型错了而是初始条件没有对齐。我还遇到过一个非常隐蔽的问题代码里某个变量明明在约束中使用了但在目标函数中没有对应的成本项导致该变量被优化器赋予了一个极端值。这类问题通过画变量曲线可以快速发现——正常调度中任何变量的曲线出现“持续顶到上限”的情况都要回头检查目标函数是否漏了它的成本项。6.3 提高代码健壮性的经验最后分享几个提升复现效率的习惯每次改完参数立刻把结果导出一份CSV存档并在文件名里标注参数版本。跑几十轮对比后你会发现版本管理比什么都重要。在模型构建和求解之间加断言比如检查所有负荷和可再生能源数据是否为正数、SOC是否在合理区间避免把脏数据喂给求解器。用固定的随机种子生成电动汽车接入时间。如果没有固定种子每次跑的数据集不同结果对比等于没对比。写在最后这篇论文复现项目最大的收获是让我对“协同调度”的理解从概念层面落到了约束和目标函数的细节层面。真正把模型跑起来之后你会直观地感受到可再生能源消纳率和系统运行成本之间的对抗关系在数学上就是目标函数里那一项惩罚系数电动汽车的灵活性在模型里其实就是几条SOC转移约束和一串充放电变量。所有抽象的概念最后都会归结为一行行可以求解的表达式。如果有读者正准备复现类似的调度优化论文我的建议是先把论文里的变量定义和公式推导逐条整理成一份自己的笔记再动笔写代码。模型构建阶段多花一小时求解和排查阶段就能省下好几天。工具链选型上优先选择文档完善的优化建模库配合开源求解器对一个几百个变量、几百条约束的调度模型来说已经足够。最后提醒一句复现不是终点能够把模型改造成你自己的研究问题才是。
阅读完成 · 觉得有帮助?
咨询建站