做微电网优化调度的时候,最让人头疼的往往不是模型本身有多复杂,而是“算完一个结果”和“结果真的敢用”之间隔着一道鸿沟。今天想聊的这个项目,就是一个典型的“日前-日内两阶段优化调度”框架,外加对电价、光伏、风电、负荷四个参数的独立敏感性分析,全部基于Matlab代码实现。这套东西我断断续续跑了大半个月,中间踩了不少坑,也把很多细节理清了,写出来分享给正在做微电网调度、储能优化、或者要交竞赛报告的各位,希望能帮你们少走点弯路。
先把这个项目能解决什么问题说清楚。微电网里接了光伏、风电、储能,还要和主网进行购售电交易,但光伏和风电出力天生不稳定,负荷预测也不可能百分之百准确。如果只做一次性的“日前计划”,到了第二天实际运行往往会偏离计划;如果完全靠实时调整,又容易让储能频繁充放、成本失控。两阶段调度的思路就是:日前先根据预测数据做一次全局规划,确定储能的充放电计划、购售电策略;日内再根据超短期预测和实测数据做滚动修正,把偏差掰回来。这个思路在业界和学术界都是主流,非常适合处理可再生能源接入后的不确定性问题。
而敏感性分析这部分,很多人会忽略,但它恰恰是项目价值最大的地方。电价、光伏、风电、负荷这四个参数,决定了调度方案的边界条件,但哪个参数对系统运行成本的影响最大?储能策略在什么情况下会“质变”?这些问题如果不做敏感性分析,心里就没底。这套代码里对四个参数分别做独立扰动,固定其他参数不变,观察目标函数和决策变量的变化幅度,找到系统的薄弱环节,从而指导电价政策制定、储能容量配置、预测精度要求设定。下面我从整体思路、模型细节、代码实现到排查经验,一步步拆开讲。
1. 项目定位与整体思路拆解
1.1 为什么是“两阶段”而不是单阶段优化
单阶段优化就是“一锤子买卖”,基于一组预测数据,把未来24小时的调度计划全部排出来。这种做法的最大问题在于,预测误差全部靠计划本身“硬扛”。举个例子,日前预测明天中午光伏出力是100 kW,实际到了第二天中午只有60 kW,那么按照日前计划安排的储能放电量和购电量必然偏少,系统只能临时从主网购高价电,或者被迫削减负荷,成本一下就上去了。
两阶段调度的本质是“先定大局,再修细节”。日前阶段以1小时为间隔(有时候也用15分钟),对全天24个时段做全局优化,目标是让全天总运行成本最低,同时满足所有约束。这个阶段用的预测数据是“中长期预测”,精度可能只有80%左右,但它决定了储能什么时候充、什么时候放,什么时候多买电、什么时候少买电,是一个整体框架。日内阶段则类似模型预测控制(MPC),每15分钟或每1小时滚动一次,基于最新的超短期预测(精度可以到95%以上)和实际测量数据,只优化未来几个小时内的决策,同时尽量不偏离日前计划太远,避免储能动作频繁、购售电曲线剧烈抖动。
用一个生活化的类比来说,日前计划就像你出门前做的旅行攻略,大致确定哪天去哪个城市、住哪家酒店;日内修正就像是到了当地后,根据天气和交通情况临时调整打车路线,但不会轻易推翻整体行程。如果把攻略做得太死,遇到突发情况就抓瞎;如果完全不做攻略,走一步看一步,又会多花冤枉钱。两阶段调度就是把“计划”和“修正”结合起来,兼顾全局最优和局部应变。
1.2 四个敏感性参数的选取逻辑
为什么偏偏选电价、光伏、风电、负荷这四个参数做敏感性分析,而不是储能效率、电池容量、联络线功率上限这些?因为前四个是系统的“外部输入条件”,后几个是“系统自身属性”。
外部输入条件在真实运行中是不可控的:电价由市场或政策决定,光伏和风电由天气决定,负荷由用户的用电行为决定。这些参数每天都在变,而且变化幅度可能很大。系统属性则相对固定,储能容量装了多少就是多少,充放电效率在短期内基本不变。所以,要评估一个调度策略在不同外部环境下的适应能力,最有效的办法就是对外部输入参数做扰动,观察系统响应。这就像测试一辆车的性能,你会改变路况(外部条件)来试,而不是拆掉轮胎换座椅(内部属性)来试。
另外,这四个参数的“不确定性来源”也各不相同。电价的不确定性来自于市场波动或政策调整,光伏来自于云层遮挡,风电来自于风速随机变化,负荷来自于用户行为随机性。它们分别代表了市场类、气象类、行为类的不确定性,覆盖了微电网面临的主要风险来源,做出来之后分析视角会很全面。
1.3 整体技术路线与数据流转
这套代码的整体流程可以概括成五个环节:数据准备、日前优化、日内修正、敏感性分析、结果可视化。
数据准备阶段需要输入:光伏预测出力曲线、风电预测出力曲线、负荷预测曲线、分时电价信息、储能系统参数(容量、功率上限、SOC上下限、充放电效率)、联络线功率上限。这些数据在代码里统一整理成列向量或矩阵,方便后续调用。需要特别说明的是,如果手上没有真实的微电网数据,可以用典型日数据或者直接构造测试数据,不影响方法验证。
日前优化阶段调用求解器,计算出各时段的储能充放电功率、购售电功率、SOC变化轨迹,并保存为“日前计划”。日内修正阶段按时间窗滚动推进,读取新预测数据,以“尽量贴近日前计划”为附加目标,求解当前窗口内的最优调整量,然后将第一时段的控制指令下发,窗口后移。敏感性分析阶段则是把所有参数恢复到基准场景,循环执行“单参数扰动-重跑优化-记录结果”,最后用曲线和表格展示不同参数对总成本和调度策略的影响程度。
这套流程里,数据传递是整个工程的“血管”。我建议从一开始就把输入数据结构化,统一用结构体(struct)或者表格存,别东一个变量西一个变量,否则后面做批量循环的时候会非常痛苦。后面我在踩坑环节还会详细讲这个问题。
2. 日前阶段建模与核心实现细节
2.1 目标函数与约束条件设计
日前阶段的目标函数,我采用的是“运行成本最小化”模式,核心包含三项:与主网交互的购售电成本、储能充放电造成的折算损耗成本、弃风弃光的惩罚成本。
购售电成本是最主要的部分,计算公式是每个时段购电价格乘以购电量减去售电价格乘以售电量,再累加全天。这里有个坑:购电和售电不能同时发生,否则就会出现“一边买一边卖”的荒谬场景。为了处理这个市场约束,我引入了两个互斥的二进制变量,分别表示购电状态和售电状态,并强制二者之和不大于1。储能损耗成本采用充放电功率的绝对值乘以一个等效成本系数,用来抑制储能过于频繁地充放,因为实际运行中,电池是有寿命损耗的。弃风弃光惩罚成本则是给“允许丢弃一部分可再生能源”留了一个口子,保证模型在某些极端场景下有可行解,而不是因为必须全额消纳而导致无解。
约束条件这块,最重要的几条我列一下:
- 功率平衡约束:每个时段,光伏出力、风电出力、储能放电功率、购电功率之和,等于负荷功率、储能充电功率、售电功率之和。这是整个模型的核心等式约束。
- 储能SOC递推约束:下一时段的SOC等于当前SOC加上充电电量(乘以充电效率)减去放电电量(除以放电效率),再除以电池容量。效率参数不能省,它直接决定了储能的“盈利空间”。
- 储能运行边界:充放电功率各有上限,且不能同时充放(用二进制变量互斥);SOC保持在设定范围内,比如0.2到0.9。
- 联络线传输功率约束:购电和售电功率都不能超过联络线允许的最大值。
- SOC始末衔接约束:为了体现“日循环”特性,我设定了调度周期结束时SOC不小于初始SOC,相当于储能在一个周期内不能把电“偷吃”掉,这在实际应用中更合理。
目标函数的数学表达可以写成如下形式:
min ∑ [ c_buy(t) * P_buy(t) - c_sell(t) * P_sell(t) + γ * (P_ch(t) + P_dis(t)) + ξ * (P_pv_curtail(t) + P_wind_curtail(t)) ] * Δt
其中 P_ch 和 P_dis 分别表示储能充电和放电功率,P_pv_curtail 和 P_wind_curtail 分别表示弃光和弃风功率,γ 是储能损耗系数,ξ 是弃能惩罚系数。这里需要注意的是,所有功率量纲统一用kW,时间单位用小时,最后算出来的成本单位就是元。如果是15分钟一个时段,Δt要填0.25,别填1,这个细节我见过很多人栽跟头。
2.2 二进制变量与线性化处理
在Matlab里用Yalmip工具箱建模时,最难处理的其实是各种“互斥逻辑”。储能不能同时充放电、联络线不能同时购售电,这些都是典型的互斥约束,需要引入二进制变量。
以储能为例,我定义了两个连续变量 P_ch 和 P_dis,再配合一个二进制变量 δ_storage。充电时 δ=1,放电时 δ=0(反过来也可以,只要逻辑自洽)。约束写成:
0 ≤ P_ch ≤ δ * P_ch_max 0 ≤ P_dis ≤ (1 - δ) * P_dis_max
这里有个小技巧:如果你用的是Gurobi或Cplex这类支持混合整数规划的求解器,可以直接这样写;但如果你手上的工具箱只有 linprog(纯线性规划),那就麻烦了,因为二进制变量带来的是混合整数线性规划(MILP)。这种情况下,要么换用 intlinprog,要么把问题做一些松弛处理,但松弛之后“互斥”就不再严格成立,结果可能有瑕疵。我建议直接上Yalmip加Gurobi的搭配,稳定又省心,后面我会细说环境配置。
购售电的互斥处理逻辑类似,区别在于购售电不是系统的物理设备,而是市场规则。有些场景下,你甚至可以通过设置“售电价永远低于购电价”来让模型自动不出现同时买卖,但这个方法只在分时电价结构简单时有效。如果分时电价峰谷差很大,而售电价又高于某些时段的购电价,模型就可能出现“低价时段买入、高价时段卖出”的套利行为——这不是bug,而是储能套利的一种形式,是合理的。只是要保证同一时段不能同时买入又卖出,所以互斥约束仍然需要加,不能省。
2.3 Matlab代码框架与关键变量说明
下面这段代码是日前优化部分的骨干逻辑,我做了简化,重点展示变量定义和约束构建方式。我用的是Yalmip工具箱,求解器选择Gurobi(也可以用Cplex,语法几乎一致)。
% 参数定义 T = 24; % 时段数 dt = 1; % 时间间隔,单位小时 E_cap = 500; % 储能容量 kWh P_ch_max = 100; % 最大充电功率 kW P_dis_max = 100; % 最大放电功率 kW eta_ch = 0.95; % 充电效率 eta_dis = 0.92; % 放电效率 SOC_min = 0.2; SOC_max = 0.9; SOC_init = 0.3; P_line_max = 200; % 联络线功率上限 kW % 预测数据(示例,实际从外部读取) P_pv = [...]; % 光伏预测出力,1x24 P_wind = [...]; % 风电预测出力,1x24 P_load = [...]; % 负荷预测,1x24 c_buy = [...]; % 分时购电价,1x24 c_sell = [...]; % 分时售电价,1x24 P_pv_hat = P_pv * 0.95; % 实际可用光伏(考虑损耗) % 决策变量 P_buy = sdpvar(1, T); % 购电功率 P_sell = sdpvar(1, T); % 售电功率 P_ch = sdpvar(1, T); % 储能充电功率 P_dis = sdpvar(1, T); % 储能放电功率 SOC = sdpvar(1, T+1); % 荷电状态,比功率多一个点 delta_storage = binvar(1, T); % 储能充放电互斥二进制 delta_grid = binvar(1, T); % 购售电互斥二进制 % 约束集合 C = []; C = [C, SOC(1) == SOC_init]; for t = 1:T % 功率平衡 C = [C, P_pv_hat(t) + P_wind(t) + P_dis(t) + P_buy(t) == ... P_load(t) + P_ch(t) + P_sell(t)]; % 储能递推 C = [C, SOC(t+1) == SOC(t) + (eta_ch * P_ch(t) - P_dis(t) / eta_dis) * dt / E_cap]; % 储能边界 C = [C, 0 <= P_ch(t) <= delta_storage(t) * P_ch_max]; C = [C, 0 <= P_dis(t) <= (1 - delta_storage(t)) * P_dis_max]; C = [C, SOC_min <= SOC(t+1) <= SOC_max]; % 购售电边界 C = [C, 0 <= P_buy(t) <= delta_grid(t) * P_line_max]; C = [C, 0 <= P_sell(t) <= (1 - delta_grid(t)) * P_line_max]; end % 周期末SOC回收约束 C = [C, SOC(T+1) >= SOC_init]; % 目标函数 objective = sum((c_buy .* P_buy - c_sell .* P_sell + ... 0.02 * (P_ch + P_dis)) * dt); % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 0); optimize(C, objective, ops); % 提取结果 P_buy_opt = value(P_buy); P_sell_opt = value(P_sell); P_ch_opt = value(P_ch); P_dis_opt = value(P_dis); SOC_opt = value(SOC);这里的 sdpsettings 里 verbose 设为 0 是为了不让求解器刷屏,调试时可以设成1,能看到求解进度。初始步数多的时候,Gurobi的日志能看到gap下降过程,比较有参考价值。还有一点,SOC(1) 对应0点的初始荷电,SOC(T+1) 对应24点的荷电,维度上比功率多一个,建模的时候容易搞错索引,建议在代码旁边加注释,标注清楚哪个变量对应哪个时刻。
3. 日内阶段滚动修正与两阶段衔接
3.1 日内修正的出发点
很多第一次接触两阶段调度的人会问:既然日前已经优化出全天计划了,为什么日内还要再算一次?直接用日前计划不就行了?
问题的关键在于预测误差。日前用的光伏预测是前一天做出的,第二天云一来,预测可能就偏了30%;负荷的日前预测也常常在高峰时段低估。如果坚持使用日前计划,储能可能因为SOC不够而无法应对突发事件,或者光伏突然多发了、储能还在按原有计划充电,导致弃光。日内修正就是干这个用的。
日内修正的典型做法是滚动时域优化,我在代码里采用了1小时滚动、4小时预测时域的配置,相当于在每一个新时刻,基于最新的超短期预测,重新求解未来4小时的调度策略,但只执行第一时段的决策,然后下一时刻窗口继续往前推。这样既利用了超短期预测的高精度,又不至于因为计算耗时太长而丧失实时性。
3.2 与日前计划的偏差惩罚机制
日内模型在结构上跟日前模型基本一致,但目标函数里多了一项“对日前计划的偏差惩罚”。这个设计非常重要,如果没有这一项,日内优化很可能为了追求局部最优而让储能大幅偏离日前计划的SOC轨迹,结果就是所有时段都在大幅度调整设备出力,虽然单个时段看起来是“最优”的,但整个系统在设备层面缺乏稳定性,储能和联络线的磨损也大。
我在目标函数中加入的偏差项是这样的形式:
- λ_soc * (SOC(t) - SOC_dayahead(t))² + λ_grid * (P_grid(t) - P_grid_dayahead(t))²
其中 λ_soc 和 λ_grid 是权重系数,P_grid 是购售电净功率(购正售负)。权重系数我建议调成比运行成本项小一个数量级,让模型“优先保证成本最优,其次兼顾计划稳定”,否则修正性能被牺牲太多。实际调参时,我试过 λ_soc 取0.1、λ_grid 取0.05,效果比较平衡。如果发现日内结果和日前计划差异太大,就调大系数;如果发现SOC轨迹虽然稳定但总成本明显上升,就调小系数。
这里需要说明一下,二次项(平方项)会让模型变成二次规划(QP),Gurobi和Cplex都能直接处理,但如果你用的是纯线性规划求解器,可以把它近似成绝对值项:λ * |SOC(t) - SOC_dayahead(t)|,然后用引入辅助变量的线性化方法处理。实际效果差不多,但求解速度更快。
3.3 滚动窗口的数据更新方式
滚动优化的代码实现核心是“循环”。下面是我在Matlab里写的一个简化滚动流程:
for k = 1:T_roll % 当前时刻 k % 获取未来 H 时段的预测数据(超短期) P_pv_pred = P_pv_ultra(k:min(k+H-1, T)); % 其他预测数据同理 % 构建日内滚动优化模型,约束条件继承当前实际状态 % 例如 SOC 初值取上一个时刻实际SOC,而不是日前计划的SOC % 求解 optimize(C_IN, objective_in, ops); % 只执行第一个时段的决策 P_grid_actual(k) = value(P_grid(1)); P_ch_actual(k) = value(P_ch(1)); % 更新系统实际状态 SOC_actual(k+1) = SOC_actual(k) + ...; end滚动里面最容易出错的地方是“预测数据的取数范围”和“窗口边界的推进方式”。如果你在第k时刻要用未来4小时的数据,预测序列的索引就是 k 到 k+3;但实际执行只执行第k时刻,执行完以后把系统状态更新到第k+1时刻,然后进入下一轮循环。这个逻辑看起来很简单,但涉及数组切片和状态传递,稍不注意就会“越界”或者“索引对不上”。我建议单独写一个“状态更新”函数,把储能递推、实时功率平衡都放在里面,减少主循环体的复杂度。
另外,日内阶段获取“当前实际状态”时,切记不要直接用日前计划的SOC值,而要根据上一个时段的实际充放电功率更新SOC,这样才能真正把“反馈校正”的思路落地。很多初学者在这里偷懒,直接用计划值,那日内修正就名存实亡了。
4. 四参数敏感性分析的完整方案
4.1 敏感性分析的实验设计与实现流程
敏感性分析的目的是回答一个问题:当某个外部参数发生变化时,系统的优化结果会发生多大变化?为了单独“提取”每个参数的影响,我采用的是标准的单因素扰动法:以某一组典型数据为基准场景,固定其他三个参数不变,只把目标参数乘以一个扰动系数(0.7到1.3,步长0.1),重新跑一遍完整的“日前-日内”两阶段优化,记录目标函数值(总运行成本)、储能充放电总电量、平均购电价、弃风弃光率等关键指标。四个参数都这样跑完,就得到四组“参数-指标”关系曲线。
这套实验设计看起来简单,但有几个细节必须注意:
第一,扰动系数是对“整条曲线”做缩放,还是对“每个时段”做独立扰动?我选择的是前者,即整条光伏曲线乘以0.8,模拟的是“辐射量整体下降”的场景;如果要做“云层遮挡导致午间短时骤降”的场景,就需要对特定时段扰动,那是另一种实验设计。做报告时一定要写清楚,否则结果会出现认知混淆。
第二,电价扰动比较特殊,不能简单整条乘以一个系数。电价是和分时结构强相关的,现实中的电价变化更多是“峰谷比变化”或者“整体价格水平变化”。我做了两种扰动:一种是整体缩放(0.8倍、0.9倍、1.1倍、1.2倍),模拟市场化改革导致的整体价格波动;另一种是峰段电价单独扰动,谷段电价不动,模拟尖峰电价政策。这两种扰动方式的结论往往不同,整体缩放主要影响成本绝对值,峰段电价扰动则会影响储能“低充高放”的套利积极性。
第三,记录结果的数据结构要统一。我在代码里用一个 table 来存放敏感性分析的中间结果,每一行对应一个扰动系数,每一列对应一个输出指标。这样画图的时候直接调用即可,不用反复从工作区找变量。建议格式如下:
| 扰动对象 | 扰动系数 | 总成本(元) | 储能放电总量(kWh) | 平均购电价(元/kWh) | 弃光弃风率(%) |
|---|---|---|---|---|---|
| 基准 | 1.0 | 3264.5 | 486 | 0.412 | 2.3 |
| 电价 | 0.8 | 2712.3 | 450 | 0.398 | 2.1 |
| 光伏 | 0.8 | 3568.7 | 530 | 0.421 | 4.8 |
针对这类数据,我会在画图时用两条线:一条是总成本变化曲线,另一条是成本变化百分比曲线。后者更直观,可以直接比出“哪个参数对成本影响最大”。
4.2 电价敏感性分析与储能套利行为
电价的敏感性分析是最有意思的。因为电价直接决定了储能“低买高卖”套利是否划算。当电价整体水平提高时,总成本上升是必然的,但储能放电总量不一定是单调变化的。
我跑出来的典型结果是:电价整体从0.8倍抬升到1.2倍时,储能放电总量呈现先增后稳的趋势。原因是电价低时,充放价差小,储能套利赚不了多少钱,系统更倾向于直接用市电;电价升高后,峰谷价差变得更有吸引力,储能就被激活了,多充多放;但电价再继续升高到某个阈值后,储能的充放电主要受到功率上限和SOC边界约束,放电量就不再明显增加了。这个“阈值效应”如果不做敏感性分析,你是很难直观想到的。
还有一种情况值得注意:如果只把峰段电价调高而谷段电价不动,储能的放电高峰时段会往后移动或者集中在峰段。这种“跨时段套利”的响应行为,直接影响到配电网侧的负荷峰谷形态。做敏感性分析时,建议把每个扰动场景下的储能SOC曲线叠加画在一张图上,能很清楚看到策略迁移的过程。
4.3 光伏与风电敏感性分析的差异对比
光伏和风电虽然同属可再生能源,在做敏感性分析时表现出来的特征差异很大。光伏出力具有明显的“时间同步性”——中午出力大,早晚约等于零。所以光伏出力整体下降时,系统主要受影响的是午间时段的功率平衡。
因为午间原本可以靠光伏多发电、少购电,甚至有余电卖出,光伏一旦下降,午间购电量立刻上升,总成本响应非常灵敏。但有意思的是,如果光伏出力上升,系统反而可能增大弃光率,因为储能和联络线都有限度,多余的光电只能放弃。这时候敏感性曲线会出现一个“平台段”:光伏再增加,成本几乎不变,因为成本瓶颈已经被储能容量和联络线容量卡住了。
风电的特性和光伏不一样,风电出力的“削峰”和“填谷”作用比较随机,一天24小时都可能有大风或小风。风电出力减少时,系统面临的是全天候的供电压力,储能需要更早地充电、更晚地放电,SOC曲线整体会变得“紧绷”。风电的敏感性分析曲线通常没有光伏那种明显的平台段,而是近似线性,这是因为风电对全天各时段的贡献相对均匀,不像光伏那样集中在特定时段。
这两组曲线的对比能说明一个很实际的问题:系统对光伏参数的瞬态变化更敏感,对风电参数的持续变化更敏感。如果要做预测精度投资,应该优先提高午间时段的辐射预测精度;如果想提升系统抗风风险能力,则需要留出更充足的储能备用。
4.4 负荷敏感性分析与系统薄弱环节识别
负荷参数的变化反映的是用户用电行为的变化。负荷整体放大时,系统势必要增加购电或者增加储能放电。但在分时电价下,负荷增加的时段不同,系统响应也不一样。
如果负荷增加发生在谷段(比如电动汽车夜间充电需求增加),系统可以用很便宜的电来满足,成本增加幅度较小。但如果负荷增加发生在峰段(比如夏季空调负荷猛涨),系统被迫在峰段高价购电,或者把储能放电量提前耗尽,成本增加幅度会非常大。敏感性分析时,不应该只对整条负荷曲线做统一缩放,还应该做“时段局部扰动”——只把峰段负荷提高10%、只把谷段负荷提高10%,分别观察总成本变化。这样能找到系统的“压力测试”场景。
我在实际项目里发现一个规律:负荷敏感性曲线的斜率在峰时段系数大于1.0以后会明显变陡,说明系统在高负荷场景下的成本压力是超线性的。原因在于峰段高价购电和储能容量饱和同时发生,系统的调节手段捉襟见肘。这种识别结果,在后续做需量管理、需求响应项目时非常有参考价值。
4.5 结果综合对比与关键结论提炼
做完四组敏感性分析之后,我习惯用一张综合对比表把所有结果汇总在一起,核心指标是“参数变化±20%时总成本的变化幅度”。按敏感性从高到低排序,通常是:电价 > 负荷 > 光伏 > 风电,但这个排序在不同电价结构、不同储能配置下会发生变化。
我跑的一个典型算例结果是:电价扰动±20%引起总成本约±16%的变化,负荷扰动±20%引起约±12%的变化,光伏扰动±20%引起约±5%的变化,风电扰动±20%引起约±4%的变化。这个结果说明,在这套参数下,系统对市场价格波动最敏感,对风电出力波动相对最不敏感。但也正是这种“不敏感”才更值得警惕——一旦超过某个阈值,风电的断崖式下降可能导致系统失去平衡,因为常规调节手段已经用尽。
这种分析的价值在于,它能把“拍脑袋觉得重要”的参数真正排出来一个优先序。比如,如果你预算有限,只能在光伏预测和负荷预测上选一个提高精度,那敏感性分析会告诉你选负荷预测;如果你在和售电公司谈电价合同,你也会知道自己能承受多大的电价波动范围。
5. 常见问题与排查技巧实录
5.1 求解器选择与运行环境配置
Matlab里做优化调度,我最推荐的工具组合是 Yalmip 建模工具箱加 Gurobi 求解器,其次才是 Cplex 和 intlinprog。Yalmip本身不是一个求解器,它是一个建模语言层,让用户用类似自然语言的方式描述优化问题,然后自动转换成求解器能识别的格式。
安装时最容易踩的坑是路径设置不对。Yalmip下载解压后,要在Matlab里面“Set Path”把文件夹加进去;Gurobi除了要装Python接口,还要把Matlab接口文件夹也加到路径里,并配好许可证文件。如果你没有Gurobi的学术许可证,用 intlinprog 也能求解小规模问题,但速度会慢不少。对于24时段的模型还好,一旦扩展到96时段或者加入多场景,intlinprog 可能几分钟都算不完,而 Gurobi 只需要几秒钟。
此外还有一个常见问题:Yalmip 有时会自动选择求解器,但默认选的不一定是你想要的。所以在 sdpsettings 里一定要显式指定 solver,比如sdpsettings('solver','gurobi'),否则可能悄悄用了linprog而报错,特别是含有二进制变量的模型,linprog根本解不了。
5.2 模型无解或结果异常的排查思路
两阶段模型里最麻烦的就是“无解”。我遇到过的无解原因主要有三类:
第一类是约束矛盾。比如SOC初始值设了0.3,但区间下限设了0.5,这直接无解;或者SOC(T+1) >= SOC_init 这个约束和SOC上限冲突,在极端条件下也可能无解。排查方法是先用optimize返回的solvertime和solve退出码,如果退出码不是0,再用checkset(C)检查哪些约束不满足。这个方法能定位到具体是哪条约束导致无解,非常实用。
第二类是数值问题。储能容量是500 kWh,SOC约束是0.2到0.9,对应的电量范围是100到450 kWh,量级在百级;而购售电功率可能是2 MW,量级在千级;两者相乘之后再放进约束里,矩阵条件数会变差。解决办法是对所有变量做归一化,或者把单位统一后再建模。我一般把功率单位统一用MW、能量单位用MWh,成本单位用万元,这样数值范围比较友好。
第三类问题是“看起来有解,结果却不符合常识”。比如储能既不充电也不放电,明明有分时电价差也完全不套利。这个大概率是目标函数漏了储能损耗项的同时,又把充放电效率设得太低,导致“存1度电只能放出0.85度,还要被算上损耗”,经济上就不划算了。如果希望储能被激活,把损耗系数调小,或者把峰谷价差调大,再观察结果。
5.3 敏感性分析批量循环时的效率优化
敏感性分析需要反复调用优化模型,每次重跑一次优化。如果不做优化,循环80次(4个参数,每个参数6个扰动级别,加上基准场景,大约25次)其实还好,但如果你把时间粒度降到15分钟、96个时段,每次求解可能需要几秒到十几秒,加起来就非常慢了。
我的做法是:在批量循环之前,把所有模型常量预先算好,不要在每次循环里重复初始化Yalmip对象;循环内部只修改变量的参数,不重复创建整个模型;每次求解后清空旧的sdpvar对象,用assign给变量赋初值做热启动。热启动对MILP求解很有帮助,Gurobi可以利用上次的解作为初始可行解,大幅缩短求解时间。另外,循环里尽量用预分配的矩阵存结果,不要在循环体内动态增加矩阵维度,Matlab在动态扩维时真的慢到让人抓狂。
5.4 结果可视化与报告输出技巧
敏感性分析的结果,我通常用两种图来展示:第一种是总成本随扰动系数变化的折线图,四组参数画在一张图上,纵轴用百分比表示成本变化率,一眼就能看出敏感性排序;第二种是选定某个参数扰动到0.8倍和1.2倍时,储能SOC和购售电功率的时序对比图,用来展示策略层面的响应。
画图的时候有个小建议:把不同参数的曲线用不同线型区分,同时加粗基准点(扰动系数1.0处)的标记。这样报告里截图出来,即使黑白打印也能分清谁是谁。输出表格时,如果用的是Matlab的table,可以直接用writetable导出CSV或Excel,再导入Word或LaTeX里排版,比自己手动做表靠谱得多。
最后再分享一个我自己的习惯:跑敏感性分析时,我会把基准场景的结果单独存成一个mat文件,每次扰动场景跑完,自动加载基准场景数据做对比。这个习惯帮我避免了很多次“改了参数却拿错基准”的低级错误。做这类项目,数据管理比建模能力更影响最终产出质量。
两阶段优化调度加四参数敏感性分析这套方案,我从头到尾用Matlab完整实现了一遍,整体跑通后,最大的感受是:模型本身并不神秘,难的是把每个细节做扎实——二进制变量别漏、单位别混、索引别错、批量循环别偷懒、报告图表别糊弄。其中任何一个环节出了小问题,结果都会差之毫厘谬以千里。如果你正在做微电网相关的竞赛、课题或者工程方案,我建议按这个框架先搭一套基准场景,再逐项增加细节,不要一上来就追求复杂的模型,先把日前的24时段模型算通,再扩展日内滚动,最后加敏感性分析,每一步都能有清晰的验证结果,项目推进起来会踏实很多。