微电网虚拟电厂日前优化调度:考虑需求响应与电动汽车V2G的MATLAB实现
发布时间:2026/9/12 9:25:52 作者:尧图编辑部 阅读量:1,286

1. 项目概述这个项目是电力系统方向很典型的“硬骨头”在一个包含光伏、储能、柴油机或微型燃气轮机、空调负荷和电动汽车的微网/虚拟电厂里做一天的日前优化调度让总运行成本最低同时还得保证母线电压、功率平衡、设备出力范围全部满足约束。MATLAB代码是主要载体优化部分用的是Yalmip工具箱加求解器模型里同时塞进了需求响应、电动汽车充放电甚至V2G、储能SOC递推和分时电价这几个模块。先说清楚这个代码能解决什么问题。如果只是搭一个微网模型把各台设备的功率算出来那用仿真的思路就够了不需要扯优化。但这里要的是“给定明天24小时的光伏预测、负荷预测、电价曲线提前把每台机组每小时出多少电、储能充多少放多少、电动汽车什么时候充什么时候放、空调负荷能压多少全部安排出来”这就是典型的日前调度问题。它的最终产物不是一组曲线而是一组“决策变量取值”而且是全年下来成本最优的那一组。这篇内容适合谁看一类是做微电网、虚拟电厂、综合能源系统方向的研究生需要快速搭出调度模型跑出对比算例另一类是准备电气类竞赛比如电工杯A题这种含光伏、储能、分时电价背景的赛题的学生需要一个能改能扩的代码底子。如果你完全没接触过Yalmip和优化建模也能看但建议先把线性规划、混合整数线性规划的概念过一遍否则后面读约束条件会有点吃力。我自己最初做这个项目时走了不少弯路最大的感受是这个模型难的不是数学公式而是怎么把“现实中零零散散的需求响应规则”翻译成“计算机能解的线性约束”。从建模到出结果每一步都有细节坑。下面把整个思路、代码结构和实操经验完整拆一遍。2. 整体设计思路与模块拆分2.1 为什么选择“日前调度多种响应资源”的组合微网调度的核心矛盾很简单明天负荷怎么变、光伏发多少今天不可能完全知道但功率平衡又必须实时满足。传统的做法是“固定出力储能平抑”调度员当天盯着曲线手动调整。但这种方式碰到高比例新能源接入后越来越吃力特别是负荷峰谷差拉大、电动汽车充电负荷又叠加上来单纯靠储能削峰填谷要么储能容量不够用要么电池频繁充放损耗太大。所以现在的做法是把“可调负荷”也变成调度资源。空调、电动汽车这些负荷本质上都有一定的灵活性空调房间有热惯性温度在舒适区间内波动一两度用户感知不强电动汽车的充电时间可以提前或推迟甚至可以在电价高峰时段反向放电。把这些资源纳入日前优化相当于把原来固定不变的“负荷曲线”变成“可调整的决策变量”系统调节能力一下子就上来了。这背后其实就是需求响应的基本逻辑用价格信号或激励契约引导用户调整用电行为实现削峰填谷。这套代码把两类需求响应都做了进去一类是价格型需求响应即用户会根据分时电价调整用电量另一类是激励型需求响应比如空调负荷在高峰时段被直接削减或提前预冷。加上电动汽车的有序充放电三种灵活性资源放在同一个优化框架下对不同电价场景、不同渗透率场景的对比分析就非常方便。2.2 微网与虚拟电厂的建模差异有一点容易被新手忽略微网和虚拟电厂在物理模型上不完全是一回事。微网通常有明确的物理边界有自己的配电线路、控制中心可以离网运行母线电压和线路潮流是必须算的物理量。而虚拟电厂强调的是“聚合”它不一定有物理边界可能只是把分散在不同位置的分布式电源、储能、可调负荷通过通信和调度中心聚合成一个整体对外像一座电厂一样参与市场。但在这套代码里两者用的是同一套日前优化框架只是一个把网络约束考虑得多一些另一个偏向于“聚合功率平衡”。做模型时不必死抠定义差异关键是明确你要解决什么问题。如果是做微网建议把网络约束比如节点电压上下限、线路容量加进去如果只是做虚拟电厂的日前申报那做单母线功率平衡就够了代码复杂度能降不少。2.3 代码文件结构与参数配置建议整套代码如果用模块化的思路来组织效率会高很多。我习惯把代码分成五个模块数据输入脚本、模型参数文件、优化模型搭建脚本、求解与结果输出脚本、绘图脚本。数据输入脚本负责读入光伏出力预测、负荷预测数据可以是Excel或.mat文件也可以直接写成矩阵。参数文件单独放一个脚本或者结构体变量比如para.ESS_capacity、para.PV_power、para.price这样后面对比不同场景时只改参数文件不用翻找主程序。优化模型搭建脚本是核心负责定义决策变量、目标函数和约束用Yalmip语法把数学公式一行行翻译过来。求解与结果输出脚本负责调用求解器、整理结果、计算各项成本。参数配置这块分时电价我建议至少设置峰、平、谷三段如果想做更细腻的对比可以细化成八段或二十四段。空调负荷的舒适温度范围和电动汽车的到达/离开时间这些参数直接影响优化结果一定要结合场景数据设置不要随手填。代码里我还会留一个scenario_switch变量用来切换是否启用需求响应、是否允许V2G方便快速生成对比算例。3. 核心模型解析与关键公式3.1 目标函数运行成本到底包括哪些项日前调度的目标函数一般是最小化系统总运行成本。在这个项目里成本构成主要有四块第一是购电成本即从上级电网买电的费用用分时电价乘以购买功率再累加一天24小时。这部分在电价高的时段要尽量少买但负荷必须平衡所以需要储能和需求响应配合。第二是柴油机或微型燃气轮机的发电成本通常用二次函数近似为了用线性规划求解会分段线性化或直接用一次项系数近似。我在代码里用的是线性成本系数加上启动成本如果模型考虑机组启停。第三是储能充放电的损耗成本或者叫运行维护成本。这个值一般很小但不能设成零否则优化会倾向于让储能频繁动作结果虽然满足约束但实际工程上不合理。第四是需求响应成本即调用空调负荷削减或电动汽车放电后支付给用户的补偿费用。这个价格一般设置在电价阈值附近既不能太高增加系统成本也不能太低用户没动力参与。目标函数写出来就是 min ∑(t1:24) [ C_pur(t)·P_pur(t) C_dg·P_dg(t) C_ess·(P_ch(t)P_dis(t)) C_dr·P_shed(t) C_ev·(P_ev_ch(t)P_ev_dis(t)) ]其中C_ev这项在V2G模式下如果放电补偿小于充电成本优化自然会选择少放或不放这个经济信号很有意思跑出来能看到规律。3.2 需求响应的数学建模价格型和激励型需求响应建模是整个项目里最需要花心思的部分。价格型需求响应核心是建立“电价和负荷需求”之间的弹性关系。常用的是价格弹性系数矩阵法即 P_load_res(t) P_load_base(t) · [1 ∑(s) E(t,s) · (price(s) - price_base(s)) / price_base(s)]这里的E(t,s)是自弹性和交叉弹性系数表示t时段的负荷对s时段电价变动的敏感程度。自弹性为负交叉弹性为正具体数值可以从文献里找参考一般自弹系数在-0.2到-0.5之间交叉弹性在0.1到0.3之间。这个公式翻译成MATLAB代码并不复杂但要注意需求响应后的负荷不能无限偏移要给定上下限否则优化会把负荷调到不合理的位置。激励型需求响应在代码里我主要用空调负荷体现。空调负荷的建模常用等效热参数模型ETP核心思想是房间温度变化满足一阶热动力学方程。在实际调度里不需要模拟逐秒温度动态而是简化成空调在某个时段被削减功率后室内温度会在一定时间内上升到上限通过提前预冷可以在削减时段内维持舒适温度。具体建模时要定义空调的额定功率、热容、热阻、初始温度和目标温度范围。削减量是所有空调可削减功率的聚合量用线性约束限制削减持续时间例如同一台空调一天最多削减4小时或者每削减1小时后必须有1小时恢复期。这些约束没有统一标准但要对应实际需求响应合同中的条款代码里做成参数方便改。3.3 电动汽车建模无序充电、有序充电与V2G电动汽车的建模分三个层次。最简单的是无序充电模型即电动汽车一回到家就以额定功率充电直到充满不可调度这相当于一个固定负荷。这样做基线负荷对比时很有用。第二层是有序充电模型充电功率变成决策变量只要在用户设定的离场时间前充到目标SOC就行。比如用户晚上18点到家第二天早上8点离开电池容量60kWh初始SOC 20%目标SOC 90%充电功率上限7kW。模型只需要加两个约束全天充电电量等于需求电量充电功率不超过上限离场前SOC达到目标。充电时间具体在凌晨还是晚上就完全由优化算法根据电价决定。第三层是V2G模型即电动汽车不仅能充电还能在高峰时段向电网放电。这需要加放电功率决策变量同时为了提高电池寿命通常会限制一日内的充放电切换次数。这个用二进制变量加辅助约束实现会增加求解难度。代码样例里我加了每辆EV一天最多放电两次的限制既能体现V2G削峰价值又不会让电池寿命被过度牺牲。3.4 储能与光伏的处理方式储能模型用SOC递推方程 SOC(t1) SOC(t) [ P_ch(t)·η_ch - P_dis(t)/η_dis ] · Δt / E_rated同时要加SOC上下限约束、充放电功率上下限约束、同一时刻只能充电或放电的互斥约束。互斥约束用两个二进制变量加一个sum≤1的约束实现。这个约束判断是很多新手写代码容易漏的地方一旦漏掉求解器可能同时给出又充又放的方案这在物理上是不成立的。光伏模型的标准化处理相对简单日前调度用的是预测出力曲线直接作为已知参数。需要注意的是弃光约束要不要加。如果允许弃光就给一个弃光惩罚成本优化可能会在某些时段主动切掉一部分光伏如果硬性要求全额消纳就把光伏出力设为恒定值作为一个负的负荷直接叠加到功率平衡约束里。两种做法对应不同政策场景代码里建议做成开关量。3.5 功率平衡约束与母线潮流约束功率平衡是日前调度最刚性的约束。对单母线微网来说就是每个时段所有电源出力加上购电功率等于所有负荷消耗加上储能充电功率、电动汽车充电功率 P_pur(t) P_pv(t) P_dg(t) P_dis(t) P_ev_dis(t) P_load_base(t) - P_dr_curtail(t) P_ch(t) P_ev_ch(t)这里把需求响应削减量放在负荷侧以负号形式参与平衡含义是负荷被削减了。要注意单位统一全部用kW。所有变量的下标对应的都是小时级数据Δt1小时所以功率数值等于对应的电量数值计算时不会出现积分项。如果要做带网架的微网需要加DistFlow潮流方程节点电压约束用二阶锥松弛。但这套代码以单母线为主更复杂的网络扩展我会在第五章说明。4. 实操过程与MATLAB代码实现细节4.1 环境准备与工具箱安装运行代码前需要准备MATLAB环境以及Yalmip工具箱和求解器。建议的版本组合是MATLAB R2020b以上版本搭配Yalmip最新版求解器用CPLEX或Gurobi学术版免费。如果装的是Gurobi需要额外安装Gurobi的MATLAB接口并确认gurobi_setup能成功运行。CPLEX的MATLAB接口相对老一些但稳定性不错。在命令行里测试一下Yalmip是否正常yalmiptest这个命令会列出可用的求解器状态。如果显示Solvernot found需要检查求解器安装路径是否加入MATLAB路径。很多报错问题都出在这一步建议先把这个跑通再继续。安装过程中最容易踩的坑是版本不匹配比如Gurobi 11只支持MATLAB R2022b以上版本强行在旧版MATLAB上装会出现接口报错。建议装之前去官方文档查一下支持矩阵。另外Yalmip可以通过GitHub下载最新的压缩包解压后把整个文件夹加入MATLAB路径即可。4.2 核心代码决策变量定义在Yalmip中决策变量的定义用sdpvar、binvar和intvar三种类型。在这个项目里大部分功率量是连续变量只有储能充放电互斥开关、电动汽车充放电状态切换需要用到二进制变量。% 时间尺度 T 24; % 连续变量储能充电/放电功率 P_ch sdpvar(1, T); P_dis sdpvar(1, T); % 连续变量从电网购电功率 P_pur sdpvar(1, T); % 连续变量柴油机出力如果考虑 P_dg sdpvar(1, T); % 连续变量电动汽车总充电/放电功率 P_ev_ch sdpvar(1, T); P_ev_dis sdpvar(1, T); % 连续变量空调需求响应削减功率 P_dr sdpvar(1, T); % 连续变量储能SOC SOC sdpvar(1, T1); % 二进制变量储能充放电状态1表示充电 u_ch binvar(1, T); u_dis binvar(1, T);这里有两个细节要注意。第一SOC的维度定义成1×(T1)是方便处理初值和末值约束SOC(1)是初始荷电状态是一个已知值SOC(25)是一天结束时的荷电状态通常约束它等于初始值这样储能可以进入第二天的循环调度。第二二进制变量越多求解越慢所以能合并的约束尽量合并。比如电动汽车如果只考虑充电不考虑V2G就不需要定义放电状态对应的二进制变量直接让充放电互斥约束用P_ev_ch P_ev_dis P_ev_total就好。4.3 约束条件逐条编写与注释约束条件的编写是整个代码的重头戏。建议采用“先写功率平衡再写各设备约束最后写需求响应约束”的顺序。功率平衡约束写成Constraints []; Constraints [Constraints, P_pur P_pv P_dg P_dis P_ev_dis ... P_load_base - P_dr P_ch P_ev_ch];这里P_pv和P_load_base是已知参数向量直接用拼接等式约束即可。如果用了sdpvar写的约束集合之后添加约束都用[Constraints, Constraints, new_constraint]的形式把它串起来这样到求解前统一交给optimize函数处理。储能约束包括上下限、递推关系和互斥关系% SOC上下限 Constraints [Constraints, SOC_min SOC SOC_max]; % SOC末端等于初始值 Constraints [Constraints, SOC(T1) SOC_init]; % 充放电功率上限 Constraints [Constraints, 0 P_ch P_ch_max .* u_ch]; Constraints [Constraints, 0 P_dis P_dis_max .* u_dis]; Constraints [Constraints, u_ch u_dis 1]; % SOC递推 for t 1:T Constraints [Constraints, SOC(t1) SOC(t) ... (P_ch(t)*eta_ch - P_dis(t)/eta_dis) / E_rated]; end注意充放电功率上限里的.*运算符表示“逐点相乘”这是把二进制变量当作开关使用。当u_ch(t)1时P_ch(t)才允许大于0当u_ch(t)0时P_ch(t)被强制为0。这种写法比用implies函数快很多推荐优先使用。电动汽车约束要区分是聚合模型还是单体模型。聚合模型用一个总功率变量描述所有电动汽车只限制总功率上限单车模型则用三维变量P_ev(i,t)表示第i辆车在t时段的充电功率。代码里先用聚合模型后面扩展成单车模型时只需把变量维度改一下其余逻辑完全一样% 电动汽车总充电功率不超过所有车的总功率上限 Constraints [Constraints, 0 P_ev_ch total_ev_ch_max]; Constraints [Constraints, 0 P_ev_dis total_ev_dis_max]; % 考虑V2G时一天放电量不能超过充电量初始电量的部分防止无中生有 Constraints [Constraints, sum(P_ev_dis) - sum(P_ev_ch) total_ev_battery_capacity * 0.2];第二个约束是我在实际建模中觉得比较关键的如果不约束电动汽车“总放电电量不能超过总充电电量过多”优化可能会通过大量放电套利导致结果不真实。具体放多少电量取决于电池容量和用户设置的最大放电深度0.2是保守值可以调。需求响应约束方面空调削减功率上下限、累计削减时长限制写成% 空调削减功率上下限 Constraints [Constraints, 0 P_dr P_dr_max]; % 每时段削减量不能超过该时段空调基础负荷的一定比例 Constraints [Constraints, P_dr 0.3 * P_load_ac_base]; % 累计削减量限制全天总削减量不超过总负荷的一定比例 Constraints [Constraints, sum(P_dr) 0.15 * sum(P_load_ac_base)];这里的比例参数要根据场景设参考值是空调负荷弹性系数和合同约定的可中断比例。建议多跑几组不同比例的结果做灵敏度分析输出表格对比总成本变化。4.4 求解设置与结果输出Yalmip调用求解器的语句非常简单ops sdpsettings(solver, gurobi, verbose, 2, debug, 1); optimize(Constraints, Objective, ops);debug, 1这个选项建议打开。如果模型冗余约束导致无解Yalmip会输出具体是哪一行约束导致的问题非常有助于排查。求解完可以用value()提取变量的数值结果P_ch_opt value(P_ch); P_dis_opt value(P_dis); P_pur_opt value(P_pur);结果输出建议用一个结构体统一收纳然后分别计算购电成本、发电成本、需求响应成本、总成本等指标再绘图和导出Excel表格。绘图部分用stairs画24小时阶梯曲线非常直观比用plot好看很多。如果能画出“负荷曲线光伏储能购电”的堆叠面积图一眼就能看出电价高峰时段的调度策略。4.5 完整运行流程示例以典型夏日的场景举例。光伏出力峰值设在400kW中午12点基础负荷峰值500kW晚上19点电价峰值出现在10:00-12:00和18:00-21:00两段储能容量500kWh最大充放电功率100kW电动汽车总共50辆每辆电池容量60kWh18:00统一到家次日8:00离开。第一步运行参数设置脚本把上述数据全部写入para结构体。第二步运行模型搭建脚本先定义变量再写约束。第三步求解通常在几秒到几分钟内完成取决于二进制变量数量。第四步输出结果对比“无需求响应无序充电”和“有需求响应有序充电V2G”两种场景的总成本。跑完之后你大概率会看到有序充电需求响应场景下购电成本明显下降因为电动汽车充电被优化到了凌晨低谷时段空调削减集中在两个电价高峰V2G是否放电取决于放电补偿价格和电池损耗成本之间的权衡。这些结论都是可以定量展示出来的非常适合写进论文或竞赛报告。5. 常见问题与排查技巧实录5.1 模型无解或求解器报Infeasible这是出现频率最高的报错。有几个排查方向和手段最常用的手段是让Yalmip告诉你哪个约束不可满足设置debug, 1重新运行。Yalmip会用红色高亮标注冲突约束。如果还是看不出问题就采用“约束逐条注释法”先把所有约束注释掉然后一段一段加回来每加一段就求解一次。这样能快速锁定是储能约束、电动汽车约束还是功率平衡约束导致的不可行。还有一个隐藏比较深的坑是SOC初始值设置和功率平衡约束不一致。比如初始SOC是0.2但你在递推约束里算出来的SOC(2)变成了负值说明放电功率过大。这类问题要靠检查SOC序列是否越界来排查可以先用value(SOC)打印出来逐小时检查。另外注意如果分时电价曲线和负荷曲线写反了单位千瓦时写成了兆瓦时功率平衡约束也会出现数量级离谱的情况。统一单位非常关键。5.2 求解结果出现同时充放电如果看到储能“边充边放”、电动汽车“边充边放”大概率是漏了互斥约束或者二进制变量与连续变量没绑定。需要检查有没有写这两行u_ch u_dis 1; 0 P_ch P_ch_max .* u_ch; 0 P_dis P_dis_max .* u_dis;如果写了还是出现检查一下u_ch和u_dis的名称有没有拼写错误变量替换导致约束没真正加上。这类问题用value(u_ch)和value(P_ch)对比就能发现。还有一种情况是充放电功率上限值设成了标量而不是向量MATLAB在做矩阵逐点乘法时会自动扩展但如果两个变量维度不一致Yalmip可能不会报错而是产生一个错误维度的约束。建议把所有参数都预先扩展成1×24的向量再参与约束运算。5.3 求解时间过长的优化技巧随着电动汽车数量增加和V2G普及二进制变量数量会成倍增长。求解时间从秒级跳到分钟级甚至内存溢出。常用改进措施有三条。一是设置求解器的MIP gap容忍度。以Gurobi为例在sdpsettings里加上gurobi.MIPGap, 0.01表示允许1%的最优性差距。电力系统日前调度对最优性要求并不苛刻0.1%到1%的差距完全可接受但求解时间能大幅缩短。二是去掉不必要的二进制变量。如果只是做有序充电不允许V2G电动汽车完全不需要二进制变量充放电互斥的约束也不用加。实际上有序充电只需要限制充电功率上限和总充电电量即可这是一个纯线性规划问题求解速度快一个数量级。三是用聚合模型替代单体模型。50辆电动汽车如果每辆都是独立变量就是50×24个连续变量加50×24个二进制变量如果采用聚合模型只有24个连续变量加少量二进制变量。如果论文不需要展示每辆车的SOC轨迹用聚合模型已经足够说明问题。5.4 结果曲线的“锯齿跳变”原因分析有读者跑完代码后反馈储能的充放电曲线呈高频抖动一会儿充电一会儿放电。这种情况通常是目标函数里缺少对储能动作次数的惩罚。如果不加这个惩罚储能为了利用每一段微小的电价差会频繁切换状态虽然总成本最低但实际工程中电池寿命损耗很大决策结果不具备可实施性。解决方案是给储能充放电功率加上一个小惩罚项比如每千瓦时0.01元或者对切换次数加限制但这就引入了另一个二进制变量集合求解时间会上升。我建议先加成本惩罚项这个思路简单且对线性模型友好实测效果已经足够平滑。同理电动汽车V2G的频繁充放也可以通过惩罚项抑制。5.5 参数灵敏度分析的小技巧好的调度结果不能只看单一场景还要做参数灵敏度分析。最常用的分析维度是需求响应比例从10%逐步增加到30%观察总成本和削峰率的变化电动汽车渗透率从0到50台逐步增加观察负荷曲线形状和新增成本。做这个分析时不要每次手动改参数重跑代码建议写一个外层的for循环批量修改para结构体并保存每组结果。每组结果存成result(i).total_cost这样的结构体数组最后一次性绘出多条对比曲线。这样论文里需要的对比表格和曲线都能自动生成比手动重复跑几十次省力得多。6. 扩展方向与个人实操心得目前这套模型的扩展空间很大。如果后续要往深度走建议优先考虑三个方向一是把单母线扩展成IEEE 33节点配电网模型加入DistFlow潮流约束做考虑网络阻塞的日前调度二是在需求响应模型中加入空调的详细ETP模型温度变化逐时段仿真可以更精确地评估舒适度约束对调度结果的影响三是加进旋转备用约束应对光伏预测误差把确定性优化扩展成两阶段鲁棒优化或随机优化。我在实际使用中还有一个体会这类代码最终的“护城河”不是目标函数多复杂而是对实际约束的理解够不够细。以电动汽车为例如果不考虑用户在出行前的SOC需求优化出来的调度曲线就是让车一直在低谷时段充电看起来很完美但用户第二天早上根本开不了车。这类约束必须在模型里体现否则结果没有实际价值。最后分享一个小技巧调试阶段把时间尺度从24小时缩成3小时所有功率参数缩小一个数量级运行速度快非常多逻辑验证通过后再放大回24小时。我靠这个方法省了大量等待时间也更容易看清每个约束是否真正生效。如果参数设计得合理这个缩放过程不需要改动任何建模逻辑只改输入数据即可。