ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微电网日前经济调度:风光储能与需求响应的Matlab实现

微电网日前经济调度:风光储能与需求响应的Matlab实现 做微电网调度的人都知道一个尴尬风电光伏装得越多弃风弃光问题越头疼负荷高峰叠加电价峰段买电买到肉痛。我以前在课题组里被这个题目折磨过整整一个学期后来用Matlab把基于风光储能和需求响应的微电网日前经济调度完整跑通后才发现问题的关键不在于算法多高深而在于把源荷储的约束搭扎实、把需求响应的经济信号算明白。这篇文章就把我踩过坑之后的完整思路、数学模型、代码骨架和排查经验一起写出来适合正在做微电网优化、储能调度或者需求响应方向毕业设计的同学也适合刚把Matlab装上、想找一个能落地的调度例子的工程师。1. 项目整体设计与调度思路1.1 为什么先做“日前”而不是做实时微电网日前经济调度通俗说就是提前24小时把未来一天每个时段里光伏发多少、风电发多少、储能什么时候充什么时候放、哪些负荷可以挪走、哪些负荷可以少用全部排成一个可执行计划。这个计划的优化目标很直接让整个微电网的日运行成本最低同时保证供电可靠。很多人会问为什么不直接做实时调度我的理解是实时控制解决的是“分钟级偏差”问题比如风机突然出力波动、负荷瞬时抖动那是另一个层面的快速响应。而日前调度解决的是“小时级资源分配”问题电价曲线是提前公布的风机明天的出力是可以预测的负荷高峰大概几点出现也是能猜个八九不离十的。把这些“能预知的信息”用足先把大方向的计划定好实时控制只需要在这个计划附近做小范围修正这才是工程上比较合理的架构。如果你把储能、需求响应、风电光伏全部挤到实时层面去决策会出现两个问题一是求解时间不够调度指令下发可能已经晚了二是实时优化只能看到当前很短的时间窗口没法为了“晚上8点电价高峰”而提前在凌晨给储能充电。所以日前调度不是可选项它是微电网经济调度的主骨架。1.2 微电网源荷储协同的经济逻辑一个典型的微电网包含四个基本部分源风电、光伏、荷基础负荷可调度负荷、储电池储能、网与主网的连接点。传统做法是各管各的发电预测出来了负荷预测出来了储能按固定策略充放不够的电就从主网购。这种“开环”做法的最大问题是储能和负荷都没有主动参与调节风光大发时段可能白白弃电电价峰段又只能硬着头皮买高价电。把需求响应加进来之后逻辑就变了。储能干的是“搬电”的活低电价时段充电高电价时段放电赚的是峰谷价差。需求响应干的是“挪负荷”的活把高峰时段的可平移负荷挪到风光大发或电价较低时段把部分可削减负荷直接砍掉用户获得补偿微电网减少购电压力。两者其实是一个互补关系——储能容量有限充放电功率也有限遇到短时高峰它可能顶不上去而需求响应可以直接降低高峰负荷需求反而比储能更灵活。我算过一笔简单的账假设峰谷电价差是0.5元/kWh储能循环效率按90%算那么每搬移1度电的理论收益大约只有0.45元如果再算上电池循环寿命损耗和维护成本实际收益还要再低。而需求响应如果每削减1度电只补偿用户0.3元微电网直接节省的是1.2元的高峰购电价性价比明显更高。这也是为什么“风光储能”和“需求响应”要放在一起做联合优化而不是把需求响应当成一个可有可无的附属模块。2. 数学模型搭建与关键参数标定2.1 风光出力建模预测数据不能直接用风电和光伏的出力建模在很多论文里写得特别复杂又是威布尔分布又是Beta分布但落到日前调度这个场景你真正需要的是一个时间序列未来24小时每个时段的风电可用出力和光伏可用出力。我建议第一步就是把预测曲线整理成两个数组P_w_forecast(t)和P_pv_forecast(t)单位统一成kW时间轴对齐成1小时间隔。这里有一个特别容易踩的坑预测值给的是“可用出力”不是“实际调度出力”。风电和光伏由于具备波动性在某些时段系统可能消纳不了这么多绿电那你就要决策是否弃风弃光。所以模型里必须设置两组变量一组是可用出力另一组是实际消纳出力两者之差就是弃风弃光量。如果不设置这个差值模型会以为绿电必须全消纳一旦某些时段出力过高约束就会无解。数据预处理也是重头戏。我通常用Matlab直接读Excel或CSV文件先检查有没有缺失值和异常尖峰。fillmissing和filloutliers这两个内置函数很好用一个填缺失值一个替换异常点。如果你拿到的是5分钟级数据想转成小时级可以直接用retime如果是timetable格式或写个简单均值循环。另外如果你想量化负荷数据的随机性还可以用Matlab算一下一维信息熵熵值高的时段说明负荷波动大在调度里要更保守一些不过这个属于加分项不影响主流程。还有一点要提醒所有时间序列必须保证长度一致。我在调试时经常遇到“Index exceeds array bounds”的报错十有八九就是某个数据文件少了一行或多了一行导致后续矩阵运算直接崩。2.2 储能SOC递推本质是离散时间系统储能建模的核心是SOC荷电状态递推这里其实就是个典型的离散时间系统。我习惯把24小时按1小时间隔划分SOC从t时刻转移到t1时刻的状态方程写作SOC(t1) SOC(t) (η_ch·P_ch(t) - P_dis(t)/η_dis)·dt / E_bat其中P_ch(t)是充电功率P_dis(t)是放电功率E_bat是储能容量η_ch和η_dis分别是充放电效率。注意充放电效率不能混用一个值因为实际电池的充电和放电损耗是不同的用同一个值会在日末SOC校验时出现几百kWh的偏差。SOC约束必须包含几个硬边界SOC(t)要落在SOC_min和SOC_max之间我常用0.2到0.9。各时段充电功率不超过P_ch_max放电功率不超过P_dis_max。日前调度通常要求日末SOC回到日初值也就是SOC(24) SOC(0)。为什么要设“日末回到初值”这条约束因为日前调度是一个按天闭环的优化如果你不强制SOC在一天结束后恢复原值模型就会把储能里的电全部放光导致第二天的调度计划完全失真成本看起来特别低但实际根本没法执行。你可以把它理解为饭卡余额的约束今天花掉的钱要从今天省下来的钱里补回来不能每天都靠透支过日子。另一个关键约束是充放电互斥。由于目标函数里储能成本一般按“充电放电量”计算如果不加互斥求解器可能会同时让P_ch和P_dis大于0自己充自己放白白增加损耗却没有实际意义。工程上通常引入两个二进制变量y_ch和y_dis加约束y_ch y_dis ≤ 1再用y状态去限制充放电功率的取值。这就是这个模型成为混合整数线性规划MILP的最主要原因。2.3 需求响应建模可平移负荷和可削减负荷要分开需求响应如果只做成一个“负荷可以随便调”的弹性变量那模型虽然简单但会算出很离谱的结果比如把所有负荷全挪到半夜0点。工程上可靠的做法是把需求响应拆成两类可平移负荷和可削减负荷。可平移负荷的特点是“总量不变、时段可变”。比如工厂的一条生产线今天要完成的总用电量是固定的但开工时间可以在允许的时间窗口内前后挪动相当于把某个时间段的功率块整体平移。建模时要加一条总电量守恒约束所有时段接收到的平移电量和移出的电量必须相等。否则模型为了省钱会把负荷凭空消失。可削减负荷的特点是“直接少用一部分电”。比如空调温度上调2度、照明减半这些属于用户牺牲舒适度换来的削减。模型里需要设置一个削减比例上限比如最多削减该时段基础负荷的20%同时把补偿成本写进目标函数。这样求解器就会自动权衡如果高峰电价1.2元/kWh削减补偿只要0.45元/kWh那削减就是划算的如果补偿高于电价收益模型自然会少削减甚至不削减。我建议在建模初期就把两类负荷分开哪怕是做一个简化版。原因是可平移负荷只改变负荷形状不改变总用电量它对“削峰填谷”的作用非常明显可削减负荷则既能削峰又能降低总电量但代价是用户满意度下降。两者的经济含义完全不同混在一起会严重影响调度结果的解释性。2.4 目标函数与完整约束体系这个模型的目标函数就是把一天的运行总成本写清楚每一项都对应一个可解释的物理量。我用的版本包含以下几项购电成本sum_t price(t)·P_grid(t)·dt其中price(t)是分时电价P_grid(t)是微电网从主网购电的功率。储能运行成本sum_t k_bat·(P_ch(t) P_dis(t))·dt用来反映电池循环损耗。需求响应补偿成本可平移和可削减负荷的补偿单价乘以实际调度量。弃风弃光惩罚sum_t λ_cur·((P_w_avail(t)-P_w(t)) (P_pv_avail(t)-P_pv(t)))·dt这个惩罚项非常重要它让模型尽量多发绿电而不是为了省钱盲目网购。约束体系里功率平衡约束是必须放在第一位的风电实际出力 光伏实际出力 储能放电 - 储能充电 主网购电 基础负荷 平移后的负荷 - 削减负荷。这个等式每个时段都要成立否则系统就不安全。其他主要约束还包括与主网交换功率的上限0 ≤ P_grid(t) ≤ P_grid_max。储能SOC递推和充放电互斥约束。可平移负荷时间窗与电量守恒约束。可削减负荷比例上限约束。所有连续变量的非负约束。为什么选择MILP而不是非线性规划因为目标函数和约束全部是线性的二进制变量引入后虽然问题变成了NP难但现代求解器对中等规模的MILP非常擅长而且能保证全局最优。相比之下如果你把电池损耗写成非线性函数虽然更精细但求解慢且只能得到局部最优。如果想更精细地考虑非线性因素也建议用分段线性化来逼近而不是直接上非线性求解器。如果你以后要扩展成连续非线性模型那时再去考虑KKT条件等最优性条件就可以用来验证算法是否收敛到局部极值点。但在本项目的MILP框架下过度设计这些反而会让代码变复杂。3. 基于Matlab的求解实现全流程3.1 求解器选型与环境配置intlinprog还是YALMIPMatlab里做MILP有两条路线。一条是直接用内置的intlinprog优点是环境干净只要有Optimization Toolbox就能跑不需要额外安装第三方库。另一条是用YALMIP建模再调用底层求解器比如Gurobi、CPLEX或者intlinprog。YALMIP相当于一个建模语言层能帮你把约束和目标函数组织得很清晰代码可读性和调试体验比分分钟手写A矩阵舒服很多。方案优点缺点适用场景intlinprog直接实现无需额外工具箱零配置约束矩阵手动拼易出错模型规模小、想快速验证YALMIP intlinprog建模自然易读易改需下载YALMIP并配置路径教学、入门、中小规模问题YALMIP Gurobi/CPLEX求解极快支持大规模需申请商业许可证配置略复杂大规模或反复调参的研究场景环境配置这块我遇到过不少坑尤其新版Matlab里容易出现各种奇怪的求解器调用失败。安装YALMIP后记得把目录加入搜索路径cd(你的YALMIP目录); addpath(genpath(pwd)); savepath;然后先跑一下yalmiptest确认安装成功。如果模型调用外部求解器时报许可证相关错误比如License Manager Error -8之类的提示大概率不是模型写错而是Matlab或者求解器的许可证状态异常。这时候花时间重装求解器不如先检查激活状态企业或学校授权的许可证只要正常在线激活就没问题。另外像2026b这样比较新的Matlab版本很多自带工具箱的路径发生了变化建议先执行ver(optim)确认Optimization Toolbox确实可用避免后续调intlinprog时一脸懵。3.2 核心代码脚本与建模逻辑我习惯把脚本拆成五个区域参数定义、数据导入、模型构建、求解、结果分析。不要把所有东西都写在一个脚本里否则后期改参数会非常痛苦。下面用YALMIP给一个可运行的框架示例这个框架是我在实际项目中反复用过的结构。先定义模型参数mpc struct(); mpc.nT 24; % 调度时段数 mpc.dt 1; % 时间间隔单位h % 分布式电源 mpc.P_w_forecast [数据列]; % 风电可用出力kW mpc.P_pv_forecast [数据列]; % 光伏可用出力kW % 储能参数 mpc.E_bat 300; % kWh mpc.P_ch_max 80; % kW mpc.P_dis_max 80; % kW mpc.eta_ch 0.95; mpc.eta_dis 0.95; mpc.SOC_min 0.2; mpc.SOC_max 0.9; mpc.SOC_0 0.5;然后是模型构建部分核心是利用YALMIP的sdpvar和binvar定义连续变量与二进制变量P_w sdpvar(1, mpc.nT); % 风电实际出力 P_pv sdpvar(1, mpc.nT); % 光伏实际出力 P_grid sdpvar(1, mpc.nT); % 主网购电功率 P_ch sdpvar(1, mpc.nT); % 充电功率 P_dis sdpvar(1, mpc.nT); % 放电功率 SOC sdpvar(1, mpc.nT1); % SOC序列多一个初值点 P_cut sdpvar(1, mpc.nT); % 削减负荷量 P_shift sdpvar(1, mpc.nT); % 平移负荷量 y_ch binvar(1, mpc.nT); % 充电状态 y_dis binvar(1, mpc.nT); % 放电状态约束写法要特别注意矩阵尺寸完全对齐。SOC递推约束中SOC(1)是初始值SOC(25)是第24小时结束后的值所以SOC变量长度是nT1Constraints []; % 功率平衡风电 光伏 放电 - 充电 购电 基础负荷 平移 - 削减 base_load [负荷数据列]; Constraints [Constraints, P_w P_pv P_dis - P_ch P_grid base_load P_shift - P_cut]; % 储能约束 Constraints [Constraints, SOC(1) mpc.SOC_0]; Constraints [Constraints, SOC(25) mpc.SOC_0]; Constraints [Constraints, SOC(2:end) SOC(1:end-1) ... (mpc.eta_ch * P_ch - P_dis / mpc.eta_dis) * mpc.dt / mpc.E_bat]; Constraints [Constraints, mpc.SOC_min SOC mpc.SOC_max]; Constraints [Constraints, 0 P_ch mpc.P_ch_max * y_ch]; Constraints [Constraints, 0 P_dis mpc.P_dis_max * y_dis]; Constraints [Constraints, y_ch y_dis 1]; % 需求响应约束 Constraints [Constraints, sum(P_shift) 0]; % 平移电量守恒 Constraints [Constraints, -20 P_shift 20]; % 每小时最大平移量 Constraints [Constraints, 0 P_cut 0.2 * base_load]; % 削减上限20%目标函数按经济含义逐项相加最后调用求解器price [分时电价数据列]; lambda_curtail 0.5; % 弃风弃光惩罚系数 k_bat 0.01; c_dr 0.45; % 需求响应补偿 Objective sum(price .* P_grid) ... k_bat * sum(P_ch P_dis) ... c_dr * sum(P_cut abs(P_shift)) ... lambda_curtail * sum(mpc.P_w_forecast - P_w mpc.P_pv_forecast - P_pv); ops sdpsettings(solver, gurobi, verbose, 1); res optimize(Constraints, Objective, ops);代码里有几个地方需要特别解释。P_shift加上abs()是为了让可平移负荷无论正向还是反向都计入调度的经济评价但YALMIP对绝对值函数会自动引入辅助变量初学者要注意这个操作会增加模型复杂度。更规范的办法是定义“移入”和“移出”两个非负变量把绝对值拆开这样模型更清晰。P_w P_pv P_dis - P_ch P_grid ...这个等式如果加上mpc.dt会导致单位错误因为功率×时间才是能量而功率平衡本来就是瞬时功率平衡不需要乘时间这也是常见报错点。3.3 参数标定与典型运行结果示例模型能不能跑出合理结果很大程度上取决于参数标定。这里给一套我在园区微电网案例里用过的典型参数供你参考调整参数名称数值说明风电装机容量200 kW可用出力预测峰值约为装机70%-90%光伏装机容量100 kW夜间为0午间达到峰值储能容量300 kWh可支撑80 kW充放约3-4小时储能最大充放电功率80 kW由PCS变流器决定购电功率上限500 kW受并网协议限制SOC范围0.2-0.9避免过度充放削减负荷比例上限20%不能牺牲太多用户体验分时电价按常见的峰平谷设置时段电价元/kWh23:00-次日06:000.3506:00-08:0011:00-18:0021:00-23:000.7008:00-11:0018:00-21:001.20我把这套参数跑过一版结果里几个特征特别明显购电成本较“无优化策略”下降了约13%高峰时段购电功率下降了近40%储能呈现出“谷段充电、峰段放电”的日内循环需求响应主要作用在18:00-21:00晚高峰可削减负荷和可平移负荷同时被激活有效避免购电功率触顶。这个结果看起来顺理成章但如果没有需求响应参与只靠储能搬移电量成本下降幅度通常只有7%左右——这也是联合优化价值最直观的体现。需要强调上面这些数字是示例数据下的典型结果不是通用结论。你的风光预测曲线、负荷曲线、电价参数一旦变化最优解的形状会随之改变所以不建议直接照搬数值关键是看趋势和边际变化。4. 常见报错、调优与结果校验技巧4.1 求解慢或者不收敛的排查路径模型写好后最常见的抱怨是“求解器跑了半天没结果”或者“返回info变成infeasible”。先别急着怀疑算法大概率是模型本身有毛病。第一检查约束是否矛盾。比如你把SOC_min设成0.5同时又要求SOC(25)回到0.2初值那模型必然无解。这种低级错误我犯过不止一次排查方式很简单把所有约束逐条注释掉再求解看哪条约束导致无解。第二检查大M值。如果你用大M法绕开二进制变量之间的耦合M值不要取得太大。比如充放电功率上限是80kWM取100足够了取100000会引入巨大的数值噪声导致求解器在可行域边缘反复震荡。第三检查二进制变量数量。24小时调度里如果每个时段都要引入多个二进制变量像充放电状态、需求响应状态、机组启停状态变量数量可能直接破百。对Gurobi这类求解器几百个二进制变量没问题但当你把场景扩展到多日甚至8760小时二进制变量上万时就要想办法削减。常用的手段包括固定明显不该同时出现的组合、用聚合时段减少时间分辨率、提高求解gap到1%左右。第四尝试给求解器一个初值。YALMIP的assign和initializedata可以指定变量初值尤其当你对调度结果有大致预期时一个好的起始点能把求解时间缩短一半以上。还有一个小技巧先用24小时单日数据调试确认模型正确后再跑多日或全年数据。如果单日模型都跑得慢那说明约束写法有问题而不是数据量大的问题。4.2 数据噪声与场景鲁棒性处理数据质量对日前调度结果的影响往往比求解器选型更大。我在实测数据里遇到过几种典型问题负荷曲线在某个时刻突然跳变到正常值的3倍风电预测序列在清晨出现一段0值光伏出力数据存在一整天的缺失。这些脏数据如果直接喂给模型结果基本没法用。我现在的处理流程是先用isoutlier找出异常尖峰再用filloutliers把异常点替换成相邻时段的均值缺失的时段用fillmissing的spline插值补上。处理完以后把全天总电量、峰谷差、均值这些统计量打印出来和原始数据对比一遍确认没有数据被严重扭曲。另外我建议在做正式结果之前人为构造“反事实场景”。比如把风电预测上下浮动20%把负荷预测上下浮动10%重新求解模型观察成本变化幅度。如果成本波动很大说明方案对预测误差很敏感那么单纯依赖确定性日前调度是不够的至少要在实际运行中保留实时修正余量。更进一步你可以把晴天、阴天、大风天几个典型日的数据分别求解再按概率加权得到期望成本这种做法比只跑单一天的结果要扎实很多。4.3 结果合理性校验与敏感性分析结果跑出来后不要只盯着总成本数字要先做三件事检查功率平衡残差、观察SOC曲线形状、核对峰谷时段的充放电方向。功率平衡残差在数值上应该接近机器精度SOC曲线应该始终在上下限内且首尾相连峰段应该以放电为主谷段应该以充电为主。如果出现“谷段放电、峰段充电”这种反常结果赶紧检查分时电价数组是否反向排序了这个错误很隐蔽因为Matlab默认不会报错。敏感性分析是该类项目的加分项。我自己做的时候会固定其他参数把储能容量从100kWh扫到500kWh观察总成本下降幅度的边际变化再把需求响应可用比例从0%调到30%画出成本-比例曲线。通常你会看到两条曲线都有明显的“边际收益递减”现象这时候就能很清楚地回答“储能配多大”“需求响应签约多少负荷最划算”这类问题。给一个自查清单每次跑完结果后快速过一遍功率平衡约束残差是否在1e-6量级。储能SOC是否越界日始末SOC是否相等。峰段购电是否明显低于谷段购电。需求响应激活时段是否集中在高电价时段。弃风弃光量是否在合理范围而不是被惩罚系数逼成0。购电功率是否超过并网协议上限。如果这些都通过结果基本可信。最后再分享一点我自己的实际体会。项目刚开始时我总想一次性把所有约束都塞进去结果模型状态特别多连调试都不知道先查哪一行。后来改成“先跑一个无需求响应、无储能的基准模型再逐步加约束”的方式整个工程变得非常可控。另一个小技巧是把分时电价、风光预测数据、负荷曲线都单独存成CSV文件模型只负责读数据不把数据硬编码在脚本里这样每次换一天的数据只需要替换文件不用改代码。这个模型跑顺之后你可以比较自然地往滚动优化、随机优化方向扩展数据接口和约束框架都不用推翻重来。希望这些经验能让你少走点弯路。
返回列表