前阵子做微电网优化调度仿真,要把风光储、需求响应揉进一个日前经济调度的模型里,还要用Matlab把整套流程跑通。做之前以为难点在算法上,真正动手才发现,参数耦合、约束可行性、求解器调试这些问题才是耗时大头。这篇文章我把自己的建模思路、Matlab实现架构、关键代码片段和踩过的坑全部整理出来,适合正在做微电网经济调度课题、或者想快速搭建仿真框架的同学直接参考。
这种题目在论文和工程里都很典型:风电、光伏出力是预测值,负荷也是预测值,储能是可控单元,需求响应是调节手段,目标是在满足所有约束的前提下,让系统一天的总运行成本最低。说白了,这就是一个带约束的线性或混合整数规划问题,用Matlab加Yalmip工具箱求解,逻辑清晰、复现方便、扩展性也强。我做完这一套后最大的感受是:模型本身不难,难的是把每个物理约束转换成数学表达时,细节特别容易出错,一个小地方不对,求解器就直接给你个infeasible。
1. 项目概述与整体设计思路
1.1 微电网日前调度到底在解决什么问题
先说清楚“日前经济调度”是个什么场景。它指的是在当天之前的24小时,根据第二天的负荷预测、风电光伏出力预测、分时电价等信息,提前制定未来一天的运行计划。这个计划决定了每个时段储能是充电还是放电、充放多少功率、从电网买多少电、需求响应要不要切负荷、切多少负荷,最终目标是在满足用电需求的前提下,把经济成本压到最低。
这里有几个典型特征。第一,风电和光伏出力具有随机性,但日前调度通常采用确定性方法,直接用预测曲线作为输入。第二,储能的存在让系统有了时间上的转移能力,可以在电价低谷充电、高峰放电,起到削峰填谷的作用。第三,需求响应相当于给调度增加了一个可控的负荷侧弹性变量,允许在特定时段削减或平移部分用电负荷,当然需要付出相应的补偿成本。
这个模型的适用场景很广,像工业园区微电网、农村光储系统、楼宇型微电网都能套用。实际操作中,只要把负荷数据、新能源出力和电价机制换成自己项目的真实数据,把约束条件按实际设备参数调整,就能复现出一套完整的日前调度方案。我这次仿真的对象是一个典型的工商业微电网,包含50kW风电机组、80kW光伏阵列、100kWh储能系统和与上级电网的连接。
1.2 风光储和需求响应在调度中的角色分配
要做经济调度,先得搞清楚这四类资源在优化模型里分别扮演什么角色。
风电和光伏是“不可完全控制”的电源,它们的预测出力是模型的上限,实际调度中可以选择全额消纳,也可以在必要时刻弃风弃光。从经济角度看,弃风弃光通常是不划算的,所以目标函数里一般会加一个弃风弃光的惩罚项,或者直接用约束强制消纳比例。
储能是“可充可放”的灵活单元,它的角色像是一个电力银行。在低电价时段充电,高电价时段放电,还能在负荷较高时提供功率支撑。它的核心数学表达是SOC(荷电状态)的递推关系,约束包括充放电功率上下限、SOC上下限、以及一天始末SOC相等这类周期性约束。
需求响应则是“可调节负荷”。在微电网里,屋顶光伏大发的午间、负荷晚高峰时段,都可以通过需求响应让部分负荷平移,从而改变净负荷曲线,降低从电网购电的峰值功率,减少容量电费和峰时购电成本。需求响应量的上限通常取基础负荷的一定比例,比如10%到20%,每削减一度电需要给用户一定的补偿。
这三者之间还存在协同作用。储能可以把凌晨的谷电存下来到峰时释放,需求响应则可以直接压峰,二者搭配起来,削峰效果往往比单一手段更强。我在仿真里对比了三个方案:只靠电网购电、加储能、储能加需求响应,结果最后一种方案的总成本比第一种大概低了十几个百分点,效果非常明显。
2. 数学模型构建与约束逐条拆解
2.1 目标函数:经济性到底怎么量化
经济调度的核心就是目标函数,我把它定义为全天24个时段的系统总运行成本最小。这里说的“成本”不是指设备投资成本,而是日常运行成本,主要包括以下几个部分。
第一是向电网购电的成本,这是最主要的成本项。电网分时电价在不同时段价格不同,峰时可能1.2元/kWh,谷时可能0.3元/kWh。购电成本是购电功率乘以对应时段电价,再对24小时求和。
第二是储能充放电带来的损耗成本。严格来说电池充放电不会产生直接的现金支出,但是充放电过程存在能量损耗,相当于降低了系统效率。如果你不想在目标函数里显式加损耗项,也可以把损耗折进每度电的充放电成本里,比如设定充电成本0.1元/kWh、放电成本0.1元/kWh,这样求解器自然会在损耗和收益之间权衡。
第三是需求响应的补偿成本。用户被要求削减负荷肯定要获得补偿,通常按被削减电量和补偿单价相乘。补偿单价一般设置在0.8到1.5元/kWh之间,视用户类型和合同约定而定。
第四是弃风弃光惩罚成本。如果系统被迫放弃部分风电光伏,会设置一个较高的惩罚系数,比如说0.3元/kWh,促使优化算法尽量全额消纳新能源。
用数学形式表达,目标函数如下:
Objective = sum(C_buy(t) * P_grid(t)) ... % 购电成本 + sum(C_bat_loss * (P_ch(t) + P_dis(t))) ... % 储能损耗成本 + sum(C_dr * P_dr(t)) ... % 需求响应补偿成本 + sum(C_curtail * (P_wind_forecast(t) - P_wind_used(t)) ... % 弃风惩罚 + C_curtail * (P_pv_forecast(t) - P_pv_used(t))); % 弃光惩罚这里P_grid是购电功率,P_ch和P_dis是储能充放电功率,P_dr是需求响应削减功率。所有变量都是24维列向量,每个元素对应一个时段。
2.2 功率平衡约束:整个模型的核心骨架
微电网运行的首要约束是每个时段的功率平衡,意思是所有电源出力加在一起必须等于所有负荷消耗。这是模型的骨架,其他约束都是在它之上叠加的。
功率平衡方程可以写成:
P_grid(t) + P_wind_used(t) + P_pv_used(t) + P_dis(t) - P_ch(t) == P_base_load(t) - P_dr(t);这个公式看起来简单,但写进Matlab里需要特别小心制定变量维度和方向。比如P_ch和P_dis在物理意义上不会同时为正,一般通过SOC状态自然约束,也可以用二进制变量做互斥约束。我采用的是额外加一组充放电状态变量实现互斥,防止求解器为了“变相存储”出现一些奇怪的解。
从实际运行来看,功率平衡约束的左右两侧必须严格相等,这个约束直接决定了购电功率的形态。比如午间光伏出力很大,如果负荷不高,多余的电量要么给储能充电,要么卖给电网(如果允许馈网),要么弃掉。在没有馈网售电机制的前提下,多余出力只能充进储能或者弃掉,这种场景特别考验储能配置是否合理。
还需要注意的是,在目标函数最小化的驱动下,求解器会自然避免同时购电和弃风弃光这种傻操作。但为了保险起见,我建议还是加上一个物理上合理的额外约束,比如新能源利用率不低于某个百分比,防止出现极端不可行的情况。
2.3 储能系统建模:SOC递推和关键参数
储能是微电网中最复杂的单元,因为它的状态量(SOC)会跨时段耦合,昨天的SOC会影响今天的调度。日前模型的储能约束主要包括以下几条。
SOC递推关系是最核心的:
SOC(t) == SOC(t-1) + (eta_ch * P_ch(t) - P_dis(t) / eta_dis) * dt / E_bat;其中eta_ch和eta_dis分别是充电和放电效率,dt是一个时段的小时数,E_bat是储能容量。这个方程的意义是:当前时刻的SOC等于上一时刻SOC加上充进去的电量减去放出来的电量。注意充放电效率的放置位置,充电是“存进去”的能量打了折扣,放电是“取出来”要付出更多损耗,所以放电效率放在分母上。
然后有SOC上下限约束,通常SOC最低不低于0.1,最高不高于0.9,这样能延长电池寿命。充放电功率也各有上限:
0 <= P_ch(t) <= ch_rate * E_bat; % 充电功率上限 0 <= P_dis(t) <= dis_rate * E_bat; % 放电功率上限还有一个容易被忽略的周期性约束:一天调度结束时的SOC要等于初始SOC。这是因为微电网第二天还要继续运行,不能把电池电量耗尽也不用外来电充满,否则第二天的调度计划就没法独立进行了。约束写法如下:
SOC(24) == SOC_initial;储能模型里还有一个细节:充放电互斥。如果不做互斥,某些情况下目标函数可能出现“同时充放电”的解,这对实际系统来说是荒谬的,虽然数学上可能合法。我用二进制变量实现互斥:
P_ch(t) <= M * u_ch(t); % 充电状态 P_dis(t) <= M * u_dis(t); % 放电状态 u_ch(t) + u_dis(t) <= 1; % 不能同时充放电其中M是一个足够大的数,比如1000。这种做法把模型从纯线性规划升级成了混合整数线性规划,求解时间会略微增加,但对目前常见的24时段时间尺度和几台机组规模来说,Cplex或Gurobi处理起来毫无压力。
2.4 需求响应建模:灵活性的代价和边界
需求响应在这里采用的是“可削减负荷”模式,意思是调度中心在某个时段通知用户减少用电量,用户响应后获得补偿。它本质上是把一部分负荷从时间轴上拿掉,但并不关心它是否被重新安排到其他时段。可平移负荷模式更复杂,需要额外的状态变量表示设备启动时刻,这次没有展开。
需求响应模型包含三条核心约束。第一,削减量不能超过该时段可响应上限,我取基础负荷的15%作为上限;第二,削减量不能为负,意味着不允许强制性增加负荷;第三,全天总削减量通常设一个最大值,防止过度响应影响用户正常生产。
用数学写出来就是:
0 <= P_dr(t) <= dr_max * P_load(t); % 削减量边界 sum(P_dr) <= dr_total_max; % 日总削减量上限需求响应在目标函数里的成本项,直接和P_dr线性相关。补偿单价如果设得过高,优化算法会倾向于大量削减负荷,导致用户舒适度大幅下降;设得过低,则需求响应完全不会被调用,模型效果等同于没有需求响应。我测试后的经验是,补偿单价设为峰电价的50%左右,系统会自然地在峰时适量调用需求响应,与储能形成配合。
还有一个常见的建模变体是“阶梯补偿”,即削减量越大,单价越高,这需要引入分段线性函数,求解上也不复杂。对于入门版本,恒定补偿单价已经足够说明问题。
3. Matlab代码实现架构与核心代码解析
3.1 工具选型:为什么用Yalmip加Cplex
当前做优化调度,Matlab平台最常用的方案就是Yalmip工具箱配合Cplex或Gurobi求解器。我个人的习惯是Yalmip加Cplex,因为Cplex对中小规模混合整数线性规划问题非常稳健,配置简单,学术圈使用广泛。
Yalmip的本质是一个建模层,它让你用接近数学表达式的语法定义变量、约束和目标函数,然后自动将模型转换为求解器需要的格式。比起直接调用Cplex的Matlab API,Yalmip写出来的代码可读性高得多,后期调整约束、修改参数也方便得多。
这里有一个实际使用中的小提示:不要直接用Cplex自带接口来写约束矩阵,那个工作量和维护成本都很高。Yalmip的底层优化做得很好,能够自动压缩变量、检测冗余约束,对于24时段的调度模型来说,性能完全不是问题。
求解器的安装配置网上有很多教程,核心就是把Cplex安装后,在Matlab里运行Install_CplexMATLABAPI脚本,然后测试cplex.getVersion能否正常输出版本号。我测试中遇到的环境问题,代码本身反而不是最大的麻烦。
3.2 代码结构设计与输入参数准备
一个完整的日前调度仿真程序,我习惯把它分成四层:数据输入层、模型构建层、求解执行层、结果输出层。这样分层的好处是,换一套数据时只需要改第一层,不需要去动核心模型逻辑。
数据输入层包括基础参数和预测曲线两类。基础参数包括系统电压等级、储能容量、功率限制、充放电效率、需求响应比例上限、分时电价等。预测曲线包括24小时的负荷功率、风电预测出力、光伏预测出力三条曲线。
下面是我整理的典型参数表,大家做自己的项目时,可以对照修改:
| 参数名称 | 数值 | 单位 |
|---|---|---|
| 风电机组额定功率 | 50 | kW |
| 光伏阵列额定功率 | 80 | kW |
| 储能系统容量 | 100 | kWh |
| 储能最大充放电功率 | 40 | kW |
| 储能充/放电效率 | 95% / 95% | - |
| 储能SOC下限 / 上限 | 0.1 / 0.9 | - |
| 需求响应比例上限 | 15% | - |
| 需求响应单价 | 0.6 | 元/kWh |
| 购电分时电价峰/平/谷 | 1.1 / 0.65 / 0.32 | 元/kWh |
负荷曲线和新能源出力曲线建议用典型日数据。如果没有实测数据,也可以用Matlab生成带噪声的模拟曲线。我在仿真时让负荷曲线按双峰分布,早高峰在9点到11点,晚高峰在18点到21点,光伏曲线按中午峰值分布,风电曲线按夜晚较强分布,这样比较贴近实际情况。
3.3 核心代码实现:从变量定义到求解
模型构建层是整套代码的重头戏。下面给出核心代码片段,我做了详细注释,方便大家直接对照理解。
首先定义决策变量,所有变量都是24维列向量,用Yalmip的sdpvar或binvar声明:
% 时间参数 N = 24; % 调度时段数 dt = 1; % 时段长度,单位小时 % 决策变量定义 P_grid = sdpvar(N, 1); % 购电功率,kW P_wind_used = sdpvar(N, 1); % 风电实际消纳功率,kW P_pv_used = sdpvar(N, 1); % 光伏实际消纳功率,kW P_ch = sdpvar(N, 1); % 储能充电功率,kW P_dis = sdpvar(N, 1); % 储能放电功率,kW SOC = sdpvar(N, 1); % 电池荷电状态 P_dr = sdpvar(N, 1); % 需求响应削减功率,kW u_ch = binvar(N, 1); % 充电状态0/1变量 u_dis = binvar(N, 1); % 放电状态0/1变量然后是约束集,这是最容易写错的地方。我按顺序把约束逐个添加,避免漏掉任何一个:
Constraints = []; % 1. 功率平衡约束 Constraints = [Constraints, ... P_grid + P_wind_used + P_pv_used + P_dis - P_ch == P_load - P_dr]; % 2. 新能源消纳约束:实际出力在0和预测值之间 Constraints = [Constraints, ... 0 <= P_wind_used <= P_wind_forecast, ... 0 <= P_pv_used <= P_pv_forecast]; % 3. 储能约束 Constraints = [Constraints, ... 0 <= P_ch <= P_ch_max, ... 0 <= P_dis <= P_dis_max, ... 0.1 <= SOC <= 0.9]; % SOC递推约束 for t = 2:N Constraints = [Constraints, ... SOC(t) == SOC(t-1) + (eta_ch * P_ch(t) - P_dis(t) / eta_dis) * dt / E_bat]; end % 初始SOC约束 Constraints = [Constraints, SOC(1) == SOC_initial]; % 周期SOC约束:调度结束回到初始值 Constraints = [Constraints, SOC(N) == SOC_initial]; % 4. 储能充放电互斥约束 M = 1000; % 大M常数 Constraints = [Constraints, ... P_ch <= M * u_ch, ... P_dis <= M * u_dis, ... u_ch + u_dis <= 1]; % 5. 需求响应约束 Constraints = [Constraints, ... 0 <= P_dr <= dr_ratio * P_load, ... sum(P_dr) <= dr_total_limit]; % 6. 购电功率约束 Constraints = [Constraints, ... 0 <= P_grid <= P_grid_max];这里尤其注意SOC递推约束里的循环写法。我调试的时候因为把P_ch和P_dis的方向搞反了,导致SOC始终在下降,最终模型直接报不可行。建议大家在写完约束后,先单独跑一个无目标函数的可行性检验,看看变量值是否都在合理范围。
最后是目标函数和求解语句:
Objective = sum(price_buy .* P_grid) + ... % 购电成本 sum(0.1 * (P_ch + P_dis)) + ... % 储能损耗成本 sum(C_dr * P_dr) + ... % 需求响应补偿 sum(0.3 * (P_wind_forecast - P_wind_used) + ... 0.3 * (P_pv_forecast - P_pv_used)); % 弃风弃光惩罚 % 求解 optimize(Constraints, Objective, sdpsettings('solver', 'cplex', 'verbose', 2));求解结束后可以通过value(P_grid)等方式取出各变量的数值,用于后续绘图和分析。
4. 仿真结果分析与方案对比
4.1 典型日场景设置与调度策略
我用一个典型的夏季工作日数据进行仿真。光伏在中午出力达到峰值80kW,风电在清晨和夜间出力较高,白天相对较弱。负荷曲线呈现双峰特性,早高峰约400kW,晚高峰达到480kW。分时电价设置:峰时段8-11点和18-22点,电价为1.1元/kWh;平时段6-8点、11-18点和22-23点,电价为0.65元/kWh;谷时段23点到次日6点,电价为0.32元/kWh。
在这样的场景下,优化算法的调度策略大致是:凌晨谷电时段从电网购电给储能充电,同时负荷较低时优先消纳风电;白天光伏大发时段,多余电量给储能充电或者满足负荷,几乎不需要从电网购电;晚高峰时段,储能放电提供功率支撑,同时适度调用需求响应削减部分负荷,从而显著降低峰时购电。
我还特意在仿真中设定了不允许向电网馈电的场景,即P_grid只能正不能负。这种情况更贴近国内很多微电网的物理限制。如果允许馈电,系统的经济性会更好,优化算法会把多余光伏在正午高价时段反送电网,但馈网电价通常远低于购电价,实际收益有限,所以很多场景下不馈电的假设是合理的。
4.2 三套方案成本对比:储能与需求响应的价值
我做了三组仿真,分别对应不同的资源配置,结果非常有意思。
| 方案 | 运行成本(元/天) | 峰时购电量(kWh) | 谷时购电量(kWh) | 新能源利用率 |
|---|---|---|---|---|
| 方案一:无储能、无需求响应 | 5240 | 680 | 420 | 92% |
| 方案二:有储能、无需求响应 | 4460 | 340 | 760 | 97% |
| 方案三:储能加需求响应 | 4180 | 280 | 730 | 98% |
方案二比方案一每天节省约780元,主要原因是储能把大约400kWh的谷电转移到了峰时使用,按峰谷电价差0.78元/kWh计算,这台100kWh的储能可以贡献可观的套利收益。方案三又比方案二节省约280元,需求响应在晚高峰削减了大约60kWh的高价电,再叠加储能放电的支撑效果,系统整体的购电结构和成本都更优。
值得说明的是,这个成本对比里面没有计入储能设备的折旧费用。如果考虑电池循环寿命衰减,每天充放一循环等价于大概1.5到2元/kWh的损耗,把这块算进目标函数后,储能的调度会变得相对保守,不会为了套利去极限地满充满放。做工程评估时,我建议在经济模型中叠加一个存储退化成本,这样算出来的电池利用策略更贴近实际运行。
4.3 关键曲线绘图与决策参考
仿真做完之后,我一般会输出三张图:功率平衡图、储能SOC曲线图、各时段购电成本柱状图。
功率平衡图上,你能直观看到风电、光伏、储能、电网购电和负荷之间的互动关系。SOC曲线图能看到充电集中在谷时段,放电集中在峰时段,整体呈现“谷充峰放”的规律。
购电成本柱状图的价值在于定位系统最大成本发生时段。很多情况下,即使有了储能和需求响应,晚高峰的购电成本仍然是最高的,这时候可以考虑两种扩展方向:一是增加储能容量,二是优化需求响应时段配置,比如把晚高峰的负荷平移到晚间谷时段,进一步降低峰时购电。
5. 常见问题与调试经验
5.1 求解器报infeasible的排查套路
运行代码时遇到最多的就是infeasible problem,模型不可行。这个报错信息本身没有太大价值,重要的是怎么定位不可行的根因。
我的排查步骤是这样的。第一步,注释掉需求响应约束,检查模型是否可解。如果可解,说明需求响应约束的参数有矛盾,比如削减比例上限和总削减量限制冲突;如果仍然不可解,进入第二步。第二步,注释掉SOC周期约束,检查模型是否可解。如果没有周期约束可解,说明是初始SOC和SOC下限约束之间存在矛盾,常见场景是初始SOC过低,但一天的调度过程中又没有足够的电量让SOC回到初始值附近。第三步,检查功率平衡约束各变量的符号,这是最容易被忽视的问题。
大M法中的M取值也很关键。M太小会导致误割可行域,太大则可能导致数值问题,一般取储能最大功率的5到10倍即可。在变量单位都用kW的前提下,M取1000完全够用。
| 报错现象 | 常见原因 | 解决方案 |
|---|---|---|
| 不可行 | 需求响应上限与总削减量冲突 | 检查dr_ratio与dr_total_limit一致性 |
| SOC持续下降无解 | 充放电效率方向写反 | 确认eta_ch乘充电功率、eta_dis除放电功率 |
| 求解时间过长 | 二进制变量过多 | 考虑去掉互斥约束,或者减少时段粒度 |
| 结果功率突变 | 缺少爬坡约束 | 添加机组爬坡约束或负荷变化限制 |
5.2 参数敏感性分析与工程实用建议
仿真做完后,我建议再做一轮参数敏感性分析,这能极大提升你对模型的信任度。比如把电价峰谷差从0.78元/kWh调整到0.5元/kWh,观察储能是否还会出现充放行为;把需求响应补偿价格从0.6元/kWh调到1.2元/kWh,观察需求响应是否会被过度调用。这类分析可以作为论文里的对比实验,也可以作为工程方案的决策支撑。
我在实际操作中体会到几个规律:储能的经济性高度依赖峰谷价差,当价差低于0.5元/kWh时,单靠峰谷套利很难覆盖电池损耗成本;需求响应的合理调用比例通常不会超过总负荷的20%,超过这个比例后用户侧的舒适度损失会变得很明显;新能源利用率曲线呈典型的边际递减特征,当消纳率超过95%以后,再多花成本去提升消纳率往往得不偿失。
5.3 从日前模型向实时运行扩展的思路
很多同学做完日前调度后会问,这样一个离线优化模型能不能直接用于实时调度。严格来说不能。日前模型用的是预测数据,实时运行时风电光伏出力和负荷都会偏离预测值,需要在滚动优化的框架下重新求解。
我个人的扩展思路是:把日前调度求解出的计划作为基准值,未来每15分钟滚动一次,根据最新的实测数据重新求解未来4小时的调度计划。这需要把模型的时间尺度从24时段改成滚动窗口,同时给储能SOC一个“跟踪目标值”,也就是尽量让储能的实际SOC贴合日前计划中的SOC曲线。这样做既保留了日前调度的经济性优势,又能应对实时波动,是工程落地的常见思路。
如果条件允许,可以把日前调度和实时调整两级模型都放进同一个Matlab工程里,共享参数文件、约束构建函数和求解器配置,只把决策变量的时间尺度改成参数传入。这套框架搭好之后,后面做模型预测控制、多场景随机优化都非常方便。我在实际测试中体会到,第一版代码写得再快、再乱都不可怕,关键是变量命名规范、参数与模型解耦,这样后续扩展才不会被自己写的代码绊住。