
硕士论文复现可再生能源发电与电动汽车协同调度策略Matlab代码这么搞才顺电气工程方向做优化调度的同学十有八九都碰过这样的场景论文里公式写得明明白白目标函数、约束条件一套一套的真到动手用Matlab复现的时候第一步写变量都能把自己绕晕。这篇要聊的就是一个典型方向——可再生能源发电与电动汽车的协同调度策略题目在知网里一搜一大把但真正能跑出漂亮结果的复现代码其实没那么多。我复现这篇论文的初衷挺简单组里项目需要一套考虑风电、光伏和电动汽车充放电协同的日前调度模型导师直接甩给我一篇硕士论文让我先复现。断断续续折腾了两周踩了一堆坑最后把代码跑通、把结果和图都还原了顺带还做了几个扩展实验。这篇文章把整个过程拆开了写从数学模型怎么落到代码、变量怎么定义、约束怎么一条条写进YALMIP到求解器怎么选、结果图怎么分析都会聊到。不管你是正在复现论文的研究生还是做微电网调度的工程师只要你打算用Matlab做优化调度模型这篇文章里总有一些经验能直接用。1. 项目背景与调度问题定位1.1 为什么把风光和电动汽车放在一个框架里调度先说清楚这个研究到底解决什么问题。过去配电网里的负荷就是负荷发电就是发电调度员只管平衡。但现在风电、光伏接入之后发电侧变得不稳定了今天风大明天没风中午光伏满发晚上直接归零系统对灵活调节资源的需求一下子变大。电动汽车恰恰是这个场景里最有意思的角色。一方面大规模电动汽车接入电网后如果不加控制地随机充电晚高峰本来就是负荷高峰再来一波充电负荷配电网压力很大另一方面电动汽车本身带电池是一种分布式的储能资源如果通过调度引导它们在风电、光伏大发的时候充电在负荷高峰或者风光出力不足的时候放电也就是V2G那它就从“负担”变成了“资源”。协同调度策略的出发点就是同时做两件事第一合理安排常规机组和主网购电的出力尽可能消纳风光第二调度电动汽车集群的充放电行为让它们在时间和空间上配合可再生能源的出力曲线和负荷曲线。本质上是一个“源—网—荷—储”协同的优化问题EV在这里扮演的是可平移、可双向调节的灵活负荷。提示这类论文的核心卖点不在于数学模型多复杂而在于“协同”两个字——怎么把电动汽车的调度约束和风光出力的不确定性放在同一个框架里解决。1.2 原始论文的数学结构拆解我复现的这篇论文调度框架是典型的日前调度day-ahead scheduling时间尺度24小时分辨率1小时。核心模型是一个混合整数线性规划MILP目标函数是最小化系统总运行成本决策变量包括常规机组出力、从主网购电功率、电动汽车各时段充放电功率以及为表示机组启停或EV状态的0-1整数变量。论文的主要创新点在于把电动汽车的“可调度性”量化了不是简单当成固定充电负荷而是把每辆车的接入时段、初始SOC、目标SOC离开时所需电量、电池容量、充放电功率上限都纳入约束同时允许一部分车辆参与V2G放电。这样电动汽车集群在数学上就变成了一组带时间窗口的灵活资源。原始论文的全部代码约600行数据和案例都基于一个修改过的IEEE 33节点配电网算例规模不大但足以说明方法有效性。复现时我把它缩成了一个单微电网模型重点验证算法逻辑数据上做了一些调整但模型结构和论文完全一致。1.3 复现用的场景设定具体场景参数我直接写成表格方便你对照。参数项设定值说明调度周期24h日前调度单位时段1h可再生能源风电光伏出力曲线用典型日数据时序性较强常规机组燃气轮机1台出力范围30~100kW爬坡约束20kW/h主网交互支持购电/售电分时电价峰谷差明显电动汽车数量100辆不同接入/离开时间分三类通勤、商业、家用EV电池容量60kWh统一按主流车型电池容量处理充放电功率上限7kW按慢充桩考虑储能不单独配置依靠EV电池替代储能功能场景里面有一个关键细节电动汽车不是所有时间都在网上的通勤车白天不在家商业区车辆白天接入晚上离开家用车晚上接入早上离开。这种时间上的差异性恰恰是协同调度发挥作用的切入点因为调度需要根据每辆车的可用时段去分配充放电计划而不是简单地把全部EV当作全天都在线的储能。2. 协同调度核心模型与关键约束2.1 目标函数怎么搭论文的目标函数不长但每一部分都有实际意义。总运行成本由五项构成常规机组燃料成本、从主网购电成本售电收益为负、弃风弃光惩罚成本、EV电池退化成本、以及失负荷惩罚成本。常规机组燃料成本按二次函数近似线性化后变成分段线性函数。从主网购电成本就是分时电价乘以购电量再减去售电电价乘售电量峰谷电价结构下模型会自动倾向于低谷买电、高峰卖电或放电。弃风弃光惩罚成本是为鼓励消纳设置的典型值取风光上网电价的1.5倍左右。EV电池退化成本很多人会忽略但实际V2G调度必须考虑频繁充放电对电池寿命的影响论文里用了线性退化模型每充放1kWh折合电池容量损耗成本的比值。目标函数写出来是这样一个形式% YALMIP风格的目标函数定义 objective sum(sum(C_fuel .* P_gt)) ... % 常规机组燃料成本 sum(C_buy(t) .* P_grid_buy(t) - C_sell(t) .* P_grid_sell(t)) ... % 主网交互成本 sum(C_curtail .* (P_wind_forecast - P_wind_used)) ... % 弃风弃光惩罚 sum(sum(C_batt_degrad * (P_ev_ch P_ev_dis))) ... % EV电池退化 sum(C_loss * P_loss_load); % 失负荷惩罚注意这里用逐时累加而不是矩阵整体求和是因为分时电价系数在不同时段取值不同逐时相乘后累加更直观也方便你后面替换成实时电价数据。C_batt_degrad这一项是我在复现时额外考虑进去的因为原论文正文里提了但公式部分写得比较含糊加了之后结果更合理V2G不会过度使用。2.2 约束条件逐个说清楚约束条件是这个模型的核心也是最容易写错、最容易导致求解器报infeasible的地方。我按类别拆开说。第一类是功率平衡约束。每一时段常规机组出力、风光实际出力、主网购电功率、EV放电功率之和必须等于固定负荷、EV充电功率和主网售电功率之和。这个约束是整个模型的主心骨写成代码就一行Constraints [Constraints, P_gt P_wind_used P_pv_used P_grid_buy sum(P_ev_dis) ... P_load P_grid_sell sum(P_ev_ch) : power_balance];第二类是常规机组约束。包括出力上下限约束、爬坡约束。爬坡约束连接相邻时段写的时候特别注意t1时没有前一时段要么单独写要么用循环从t2开始。这一条很容易漏漏了的话机组可以瞬时从30kW跳到100kW结果看起来“很漂亮”实际没有物理意义。第三类是风光出力约束。实际使用出力不能超过预测值但可以在低位运行这就允许弃风弃光发生。加一个弃风弃光惩罚项之后模型会在“弃掉风光”和“调用更多灵活性资源”之间做权衡。很多复现结果弃风率偏高不是模型错了是惩罚系数设低了这个后面讲参数的时候会细说。第四类是EV约束。这是最有意思的部分也是写代码时最容易出bug的地方。每一辆车的SOC动态方程、SOC上下限、充放电功率上限、接入时段限制、离开时目标SOC约束以及充放电互斥约束。其中充放电互斥约束需要引入二进制变量是模型变成MILP的根本原因% u(t)为1表示充电为0表示放电 Constraints [Constraints, P_ev_ch(i,t) 7 * u(i,t)]; Constraints [Constraints, P_ev_dis(i,t) 7 * (1 - u(i,t))];如果不用这个互斥约束优化器可能在同一时段既充电又放电目标函数里两项成本互相抵消功率平衡上看似没问题实际上完全不合理。这是一个非常典型且隐蔽的建模错误。2.3 不确定性处理场景法还是鲁棒优化论文里对风光出力不确定性用的是场景法也就是生成多个典型出力场景对每个场景分别求解再按概率加权得到期望成本。这种方法实现简单但场景数量增加以后求解时间线性增长。我复现时做了一点调整主算例用确定性预测值求解这也是大多数论文对比实验的做法然后把风光出力偏差设为±15%用蒙特卡洛生成200个场景做期望成本评估和确定性调度结果对比。这样能看出确定性调度对不确定性到底敏感不敏感论文里称之为“调度方案的鲁棒性分析”。另一种思路是鲁棒优化把风光出力放在区间内用“最坏情况”保证约束满足。这种模型更保守但求解难度和保守程度都可能让结果不好看。如果只是复现论文并验证方法建议先做确定性模型再用场景法做分析最后有余力再试鲁棒模型。上来就上两阶段鲁棒优化的话很容易卡在两阶段迭代的交替求解上。3. Matlab代码实现与工程架构3.1 代码框架和文件组织复现代码的工程架构直接决定你后期调参和排查bug的效率。我强烈建议不要把所有代码塞进一个脚本里跑完就完事而是分成四个文件参数初始化文件、数据加载文件、模型构建文件、主程序文件。我实际用的文件组织是这样的|-- main.m % 主程序设置求解器选项并调用 |-- init_params.m % 所有系统参数定义 |-- load_data.m % 读取负荷、风光、电价数据 |-- build_model.m % YALMIP建模变量、约束、目标函数 |-- plot_results.m % 结果可视化这样做的最大好处是当你需要换一组负荷数据或者改EV渗透率时只需要改init_params.m模型文件一行都不用动。我见过太多人把所有内容写在一个脚本里改一个参数要滚动半天才找到位置还经常改错。实测下来分文件组织后调参效率提高了一倍不止。3.2 数据与参数怎么准备数据和参数分两套。一套是物理参数比如机组出力上限、EV电池容量、爬坡速率这些在init_params.m里写死或用结构体管理。另一套是时序数据比如24小时的负荷曲线、风光出力曲线、分时电价这些放在load_data.m里可以从Excel读也可以直接在代码里构造。这里要特别说一个数据准备的经验时序数据的“对齐”。负荷数据的采样间隔、风光预测数据的间隔、电价数据的间隔必须严格一致。论文里都是1小时但在实际复现中经常会从不同数据源拿到15分钟或30分钟的原始数据如果你不统一时间分辨率就直接喂给模型维度和逻辑都会错。我自己习惯在load_data.m最后加一个断言assert(length(P_load) 24, 负荷数据必须为24小时); assert(length(P_wind_f) 24, 风电预测数据必须为24小时); assert(length(Price_buy) 24, 购电分时电价必须为24小时);3.3 YALMIP建模与求解器配置YALMIP是Matlab里做优化建模的神器它本身不是求解器而是把优化模型翻译成求解器能理解的标准格式再调用底层求解器求解。对于MILP问题我推荐用Gurobi或者Cplex学术许可免费求解速度比Matlab自带的intlinprog快很多尤其是EV数量多、二进制变量多的时候差距非常明显。YALMIP建模的基本流程是先用sdpvar定义连续决策变量用binvar定义二进制决策变量然后用表达式构建约束和目标函数最后用optimize求解。核心代码大概长这样% 定义连续变量 P_gt sdpvar(1, 24, full); % 常规机组出力 P_grid_buy sdpvar(1, 24, full); % 从主网购电 P_grid_sell sdpvar(1, 24, full); % 向主网售电 P_ev_ch sdpvar(n_ev, 24, full); % EV充电功率每辆车每个时段 P_ev_dis sdpvar(n_ev, 24, full); % EV放电功率 % 定义二进制变量 u_ev binvar(n_ev, 24, full); % 充放电状态标识 % 约束集 Constraints []; % ... 添加约束 ... % 求解 options sdpsettings(solver, gurobi, verbose, 2); sol optimize(Constraints, objective, options);注意sdpvar(1, 24, full)里的full参数表示这是一个普通的矩阵变量不加这个参数YALMIP会默认把它当对称矩阵处理维度就变了。这个坑我刚开始写的时候踩过折腾了半天才发现所有变量都被当成了对称矩阵约束全部错乱。3.4 核心代码逐段解析EV约束是模型里最复杂的部分我单独拿出来逐段讲。每辆EV的SOC状态转移约束是这样的% SOC(t1) SOC(t) 充电效率*充电功率*dt/容量 - 放电功率*dt/(放电效率*容量) for i 1:n_ev for t 1:24 if t 1 Constraints [Constraints, SOC(i,1) SOC_init(i) ... eta_ch * P_ev_ch(i,1) / E_ev(i) - P_ev_dis(i,1) / (eta_dis * E_ev(i))]; else Constraints [Constraints, SOC(i,t) SOC(i,t-1) ... eta_ch * P_ev_ch(i,t) / E_ev(i) - P_ev_dis(i,t) / (eta_dis * E_ev(i))]; end end end这里eta_ch和eta_dis分别是充电和放电效率一般取0.95和0.92左右。需要注意SOC的初值很多论文复现结果图看着不合理往往是初始SOC设得太满或太空。通勤车早上出发时SOC一般要求80%以上所以初始SOC和离开时的目标SOC要按车型分开设。接入时段约束是另一处容易出错的地方。如果车辆在t时刻不在网那充放电功率必须为0。实现方式有两种一是直接把不在网时段的功率变量固定为0二是对约束本身加时间窗限制。我推荐第一种因为模型规模不变只是约束更紧求解更快。4. 算例结果分析与调度效果评估4.1 测试系统设置我在复现时做了三个对比场景无序充电模式EV一接入就开始充没有调度、有序充电模式EV仅充电不放电但充电时间由调度决定、V2G模式EV可以充放电完整协同。这样设置是因为论文的结论也是通过这三个场景逐步递进说明的单独看某一个场景反而看不出协同调度的价值。基础数据方面负荷峰值约350kW风电装机120kW光伏装机80kW负荷和风光的时序特性都是典型的夏季工作日曲线。分时电价低谷0.35元/kWh、平段0.65元/kWh、高峰1.1元/kWh。4.2 三种调度模式对比从结果上看三个模式的差异非常明显。无序充电模式下EV在18:00~22:00集中接入并立即满功率充电叠加原有的晚高峰负荷系统峰值负荷被进一步抬高峰谷差最大常规机组需要在高峰时段满发甚至需要从主网购电总运行成本最高风电消纳率也受到影响。有序充电模式下EV充电负荷被转移到凌晨风电大发时段和午后光伏大发时段系统峰值明显下降峰谷差缩小总成本比无序充电下降约12%。这个结果和论文趋势一致仅仅改变充电时间不要求EV放电就已经能获得可观的效益。V2G模式下模型不仅转移充电负荷还在晚高峰时段让部分EV放电替代一部分高价主网购电。总成本进一步下降相对于有序充电又下降了约8%。更重要的是V2G模式下系统在晚高峰的购电功率明显降低对主网的依赖程度显著减小体现了电动汽车作为分布式储能的调节价值。这个对比实验强烈建议你复现时保留因为它能让你直观理解“协同调度”的价值到底在哪无序只增负担、有序能削峰填谷、V2G才能实现双向互动层次感非常清楚。4.3 关键参数敏感性分析参数敏感性分析是复现论文时不需要额外写太多代码但能大幅提升论文深度的部分。我重点做了两个敏感性实验。第一个是EV渗透率。从50辆到200辆变化时有序充电和V2G的相对收益都在增大但增长速率递减。这说明EV规模越大协同调度的价值越大但边际效益递减到了一定规模之后需要配套扩容或者激励措施才能保持收益。第二个是分时电价的峰谷价差价差从0.5元/kWh变化到1.2元/kWh时V2G模式的放电量显著增加说明电价信号是引导EV参与电网互动的关键杠杆。这两个敏感性分析做下来代码改动非常小只需在main.m外面套一层循环每次修改参数重新求解记录结果。复现时建议把这个脚本保留下来后面写论文或者做汇报的时候这张敏感性分析图比任何文字描述都有说服力。5. 复现中的常见问题与排错实录5.1 求解器报infeasible多半是约束写死了我第一次把模型完整写完之后跑optimize直接返回infeasible problem当时人有点慌了。后来定位问题发现是EV的离开时目标SOC约束过于严格通勤车17:00到家、要求SOC达到90%但初始SOC只有30%、电池容量60kWh、接入窗口只有6小时、充电功率上限7kW实际最多只能充到30% 670.95/60 96.5%理论上刚够但加上功率平衡约束后无解。解决办法有两个方向一是设目标SOC时考虑物理极限不要设到超出可行范围二是给目标SOC约束加一个松弛变量。论文里通常用的是后一种思路——引入“未达成充电需求”的惩罚项允许部分车辆达不到目标SOC但要在目标函数里付出代价。这样既保证模型可解也保留了“尽量满足用户充电需求”的优化方向。排查infeasible问题的一个实用技巧是从简单模型开始逐步加约束。具体来说先把EV相关约束全部去掉求解成功后再一组一组加回来加哪一组时开始无解问题就锁定在哪一组。这个方法比在几百行约束里翻找快得多。5.2 二进制变量太多导致求解慢随着EV数量增加到200辆每个车辆每个时段一个二进制变量总共有4800个二进制变量加上连续变量和约束Gurobi求解时间会从几秒飙到几分钟甚至更久。这个问题在复现时几乎一定会遇到。优化手段主要有三个。第一把同一时段接入、相同参数的EV聚合用集群等效模型代替单车模型论文里常称为EV聚合商模型复杂度大幅降低缺点是失去了单车的差异化细节。第二利用YALMIP的约束编程功能把部分二进制变量固定比如接入时段外的充放电状态直接置0减少有效二进制变量。第三收紧求解器容忍度设置gurobi.MIPGap, 0.01这样求解到1%最优性间隙就停止速度能快3~5倍结果差距在可接受范围内。这三个手段里我建议优先用第三个因为代码改动最小只改一行配置如果追求精度再考虑聚合模型但聚合后需要重新校准结果。5.3 结果图里SOC曲线跳变数据维度没对齐有一次我画EV的SOC曲线时发现有些时段SOC出现阶梯式跳变。查了半天发现不是求解错误而是我定义变量时用了repmat把初值广播到全部时段然后又用循环重新赋值了一遍覆盖关系出错导致前后时段数据错位。后来检查发现是索引i和t的位置在矩阵中写反了。这个问题的通用排查方法是用Matlab的断点调试查看变量size尤其在sdpvar定义之后立刻检查size是否符合预期。另外建议在optimize之后、画图之前用value()函数把所有关键变量提取出来保存成struct再统一处理画图。不要直接在YALMIP变量上调用plot函数因为YALPIP变量不是寻常的数值矩阵有时候数值提取和显示都会出问题。还有一个非常容易踩的坑value()提取的是数值但如果你在后面继续使用原变量参与运算那还是YALMIP符号对象两者混用会导致维度或类型错误。这个我在写误差分析脚本时遇到过体验极为痛苦后来养成了习惯求解完立即把所有变量统一转成数值保存后续所有分析都基于数值结果进行绝不混合使用。6. 复现经验总结与扩展方向6.1 从复现到理解的几个关键点复现这篇论文之后我最大的感受是论文里的数学模型看起来简单但真正落到代码上细节决定成败。你以为难点在目标函数和约束条件实际难点在EV接入时段的处理、SOC初始化和目标SOC的设定、以及约束索引的对齐。这些细节论文里通常一句话带过但在代码里必须精确到每一个下标。另一个关键点是求解器的选型。很多同学习惯用Matlab内置的intlinprog但对这个规模的MILP问题来说intlinprog的求解效率比Gurobi和Cplex慢很多。而且YALMIP对后者的支持非常成熟配置也不需要额外做什么装好求解器后在sdpsettings里指定solver名称就行。如果你的Matlab版本比较新YALMIP的安装也很简单从GitHub下载后把文件夹加入路径即可。再有一个关于建模的体会论文公式里可能用同样的字母P代表不同含义比如论文里P_load代表负荷、P_gt代表机组出力但你在代码里一定要用能区分的长变量名不要为了省事用P1、P2这种无意义命名。代码越接近论文公式的符号体系复查的时候越省力。6.2 可以继续做的方向这套代码跑通之后扩展空间很大。我目前在做的是把模型从日前调度扩展到实时滚动调度即把日前调度结果作为参考值用模型预测控制在每个时段滚动更新后续时段的调度策略以应对风光预测误差。另一个值得尝试的方向是把目前的确定性模型改成两阶段鲁棒优化第一阶段做日前机组组合和EV状态决策第二阶段做实时功率调整最坏情况分析。如果你对主从博弈感兴趣还可以把问题扩展为上层配电网运营商制定电价、下层EV聚合商响应电价的Stackelberg博弈模型用KKT条件或迭代求解。你手上这套代码里的EV约束和功率平衡约束可以直接复用只需要把目标函数改成交叉迭代形式不需要重头开始写。最后再分享一个小技巧复现任何论文之前先把论文里的所有公式整理成一份自己的“数学符号表”把每个变量、单位、维度、含义都写清楚再对照符号表写代码。我整理完这份符号表之后整个建模过程顺利了很多很多错误在写代码之前就已经被排除了。这套思路也推荐给所有正在复现论文的人。