做配电网优化调度的都知道,分布式电源一多,传统单阶段调度就不够用了。不是模型解不出来,而是从日前预测到实际运行中间隔着很大的不确定性,光伏风电出力说变就变,储能SOC跟着乱跑,如果只做一次24小时优化,在实际执行的15分钟尺度上根本压不住。这也是我在做含分布式电源的配电网日前两阶段优化调度项目时最大的体会。这篇文章就把这套完整的Matlab实现思路拆开讲,包括模型怎么设计、约束怎么加、代码怎么组织、求解器怎么调、踩过哪些坑,给需要搭类似框架的朋友一条能直接跑通的路。下面的代码和参数都来自一个实际跑过的33节点标准测试系统,目标函数、约束和文件结构都是可以直接复用的状态。
1. 两阶段模型怎么设计:先定基准再微调
1.1 为什么单阶段配电网调度不够用
先抛结论:只要配电网里接了光伏、风电这类出力随天气波动的分布式电源,单阶段日前调度就很容易被现实打脸。我这个项目的启动原因,就是某次运行中光伏预测曲线和实际出力差了将近三成,按日前计划执行的储能策略直接把某条支路的电压顶到了上限。之后我把模型改成两阶段,才算把这类偏差兜住。
单阶段模型本质上是“一次定终身”:预测负荷和DG出力,求解未来24小时的机组组合和出力计划,然后下发执行。问题在于预测误差是客观存在的,尤其在云量变化、风速突变的时段,日前计划在实时运行中可能不满足潮流约束或功率平衡。这时候调度员只能手动切负荷或者调整机组,本质上是把不可控的风险转移给了运行环节。
两阶段模型的设计思路,简单说就是“先定基准,再微调”:第一阶段做一个完整的日前计划,确定各DG出力、储能充放电、联络线功率等基准值;第二阶段在第一阶段结果的基础上,用较短时间尺度的滚动修正来消纳预测误差。我把这个框架搭好后,再配合Matlab里的Yalmip工具箱统一建模,求解交给商业求解器,整个项目跑起来稳定很多。
1.2 两阶段到底怎么分层:信息流和工作流
很多人第一次接触两阶段模型,容易被“阶段”两个字绕晕。我这里用最直白的方式拆一下工作流。第一阶段是24小时整体优化,时间粒度取1小时,决策变量包括微型燃气轮机的启停和出力、储能各时段的充放电功率、与上级电网的交换功率、光伏风电的弃用比例等。这一阶段的输出是一组“基准运行计划”。
第二阶段在不同项目里有不同做法,我采用的方案是:以第一阶段基准值为锚点,对每个时段内部或滚动区间内的不确定性进行修正。具体来说,假设第一阶段给出t时段光伏出力计划值是P_pv_base,第二阶段引入调整量ΔP_pv,要求实际出力等于计划值加调整量,同时把调整量控制在允许范围内,目标函数设为最小化调整成本和可能的失负荷惩罚。
这样设计有两个直接好处。一是两阶段之间的信息传递非常明确:第一阶段决策变量在第二阶段退化为常数,避免了两阶段同时决策带来的变量爆炸;二是第二阶段的目标函数和约束都比较轻量,就算未来要改成多场景鲁棒优化或者模型预测控制,只需要替换第二阶段模块,不影响第一阶段结构。整个代码的运行流程很清晰:原始数据进第一阶段优化,得到基准计划后传给第二阶段,再输出修正计划和结果曲线。实际工程里如果调度周期更短,比如日内滚动,把第一阶段频度改成每4小时滚动一次也是可以的。
1.3 分布式电源的类型不同,建模方式也不同
分布式电源里,最省心的是微型燃气轮机这类可调度机组,因为它出力可以主动控制。我在代码里为它定义了出力上下限、爬坡速率和单位发电成本,本质上是传统火电机组模型的小型化。
光伏和风电属于不可控电源,但可以弃光弃风。这意味着它们的实际并网出力不一定要等于预测出力,而是要满足“实际出力≤预测可用功率”这个不等式。有点抽象的话,可以理解成水龙头:上游来水量是预测功率,你可以决定放多少水进系统,剩余的水(弃光弃风)就留在上游不参与调度。
储能是另一个要单独建模的单元,因为它有时序耦合。t+1时段的SOC由t时段SOC加上充电功率减去放电功率决定,这个递推关系贯穿整个24小时,意味着储能不是独立的小单元,而是把整个时间域串起来的一根线。稍有不慎,SOC递推写错一个下标,整条约束链就崩了。这套模型里我把储能建模成“充电、放电二选一”的可控单元,状态变量和功率约束都给了松弛处理,后面代码部分再详细展开。
2. 数学模型与约束条件拆解:每个公式为什么长这样
2.1 目标函数:运行成本最小化,但不是只有成本
第一阶段的优化目标是最小化系统总运行成本,包括向上级电网购电的费用、可控DG的燃料费用、储能运维费用,还有弃光弃风产生的惩罚项。这里惩罚项很关键,如果不加它,优化器可能会为了省钱把所有光伏都弃掉,反正弃电没有直接成本。加一个较高的单位弃电惩罚,相当于给优化器一个明确信号:能消纳的可再生能源尽量消纳,别为了省几块钱的运行费把清洁能源白白浪费掉。
第二阶段的目标函数相对简单,最小化各单元调整量的绝对值加权和,加上失负荷惩罚。调整成本系数要明显大于正常运行成本系数,否则第二阶段可能“摆烂”,靠大比例调整来满足约束,反而偏离了日前计划的经济性。我实测的取值习惯是:调整成本系数设置为正常成本的10到20倍,具体数值需要跟着算例规模微调。这里的“调整量”不是用来改变经济运行的,而是用来消纳预测误差的,所以成本系数必须高到让优化器只在必要时才动用它。
2.2 潮流约束用DistFlow分支模型
配电网通常呈辐射状结构,最适合的潮流模型是DistFlow方程。它只关心每条支路首末端的有功、无功和电压关系,相对全量交流潮流大幅简化,同时保留了电压分布和网损信息。推导过程在很多教材里都有,我直接给最终形式:以支路(i,j)为例,节点j的有功等于支路首端有功减去电阻损耗,再减去节点j下游的所有有功需求;无功方程类似;节点j的电压幅值平方等于节点i的电压幅值平方减去支路传输造成的压降。
实际建模时我做了两项处理。第一,忽略支路损耗的二次项,使约束变成线性,求解速度大幅提升;第二,把电压幅值的平方作为变量,方便用线性化或者二阶锥松弛处理不等式约束。对大多数配电网优化场景,这种简化已经足够,网损误差通常在允许范围内。如果你的算例对精度要求很高,再加回损耗项并改用二阶锥求解,代码主体框架不用大动,只需要改一行约束表达式。
2.3 关键约束和运行限制:别让模型“放飞自我”
优化模型最怕约束不全,导致结果看起来很美、实际根本不能执行。我在第一阶段里加了以下几类核心约束,每一类都有对应的物理含义。
功率平衡约束保证各节点注入功率和流出功率守恒,这是所有电力系统优化的底线。机组出力约束处理可控DG的出力上下限和爬坡限制,爬坡约束尤其不能省:光伏在午间可能出现出力骤降,如果没有爬坡限制约束,微型燃机可能被要求瞬间从20%负荷跳到95%负荷,现实中根本做不到,这种解拿出去也是废纸。
储能约束包括充放电功率上下限、SOC上下限以及SOC递推方程。充放电互斥约束我用了二进制变量,虽然增加求解难度,但是能真实反映储能不能同时充放电的物理事实。有功无功对电压的影响也要考虑:节点电压上下限约束把电压幅值控制在0.93到1.07标幺值之间,这是配电网安全运行的基本要求,如果少了这条约束,优化器可能给出电压越限的不可行运行方案。
第二阶段在基准计划上叠加调整约束:各DG和储能的最终出力等于基准值加调整量,调整量本身设有上下界,防止二次调整引发新的越限。对于失负荷场景,我还加入了切负荷变量和对应的巨额惩罚,优先保证功率平衡和电压安全,宁可少供电也不让系统进入非安全状态。
2.4 不确定性处理:两阶段如何“消化”预测误差
配电网调度中的不确定性主要来自光伏出力和负荷波动。我在这个模型里采用了一个工程上很实用的策略:第一阶段先预留一部分向上备用的空间,给第二阶段的调整留出余地;第二阶段再用调整量的约束来捕捉预测误差引起的功率偏差。
具体量化时,我参考了项目里的真实数据特征:负荷的15分钟级波动幅度大约在±5%以内,光伏出力的波动幅度受云层影响可达±30%。因此固定备用比例不能一刀切,我在代码里设计了一个简单接口,光伏波动幅度参数可以从历史数据统计得到,传给第二阶段作为调整量上下限。想上更复杂的随机规划或鲁棒优化,可以在此基础上替换场景生成模块,但两阶段整体骨架保持不变。这也是我推荐这套结构的原因:它给后续升级留了后门,但不影响眼前先把调度框架跑起来。
3. Matlab代码实现:从Yalmip建模到求解器配置
3.1 代码目录怎么组织:跑通优先,后面再优化
拿到一个优化模型,别急着写几百行脚本。我的习惯是先按功能拆文件,不然临时改一行约束要翻遍整个main。这套代码的基础目录结构大致如下:主程序main.m负责参数读取和组装模型;data目录存放网架参数、负荷曲线、DG出力曲线和系统24小时数据;solver_phase1.m和solver_phase2.m分别实现两个阶段的优化求解;plot_results.m把结果画出24小时曲线。
目录拆分并不复杂,但价值很高。至少三个好处:模型升级时不用改数据文件;更换求解器时只动主程序里的options;调试阶段可以单独跑某一阶段,不用每次都完整执行两阶段。我的经验是,先把第一阶段单独跑通并确认结果合理,再接入第二阶段,否则两阶段一起报错,你根本不知道错在哪一层。文件命名也用统一前缀,比如phase1_、phase2_,避免变量名和数据名混在一起分不清。
3.2 第一阶段Yalmip建模关键代码
Yalmip的建模语法相当直观,核心就是“声明变量→定义约束→设定目标→求解”。我把第一阶段储能建模的核心代码单独摘出来,因为这是新手最容易写错的地方。整个第一阶段还有其他约束,比如功率平衡、潮流和机组爬坡,那些在第二章已经给了数学表达,这里重点看储能部分怎么落地成代码,关键在于用一个二进制变量表达充放电互斥关系。
T = 24; % 调度时段数 % 决策变量:充电功率Pch、放电功率Pdis、SOC Pch = sdpvar(1, T, 'full'); Pdis = sdpvar(1, T, 'full'); SOC = sdpvar(1, T+1, 'full'); u_ch = binvar(1, T, 'full'); % 充电状态变量 % 储能约束 Constraints = []; for t = 1:T % 充放电功率上限 Constraints = [Constraints, 0 <= Pch(t) <= u_ch(t)*Pch_max]; Constraints = [Constraints, 0 <= Pdis(t) <= (1-u_ch(t))*Pdis_max]; % SOC递推 Constraints = [Constraints, SOC(t+1) == SOC(t) + eta_ch*Pch(t) - Pdis(t)/eta_dis]; end % SOC上下限和边界 Constraints = [Constraints, SOC_min <= SOC <= SOC_max]; Constraints = [Constraints, SOC(1) == SOC_0, SOC(T+1) == SOC_end];这段代码的核心在于用同一个二进制变量u_ch把充电和放电互斥起来:充电功率上限乘以u_ch,放电功率上限乘以1-u_ch,两个约束合起来,同一时段只能有一个非零。SOC递推式里,充电要乘充电效率,放电要除以放电效率,下标对应清楚就不会出错。这里我特意保留的二进制变量会增加求解负担,但如果你的储能单元多、对精确度要求高,这个建模方式还是最稳的,后面讲提速技巧时会说替代方案。
目标函数和求解代码如下,购电成本、燃料成本和弃电惩罚逐项累加后调用求解器:
% 目标函数:购电成本 + 燃料成本 + 弃电惩罚 objective = sum(price_buy.*P_buy) * interval ... + sum(cost_gas.*P_MT) * interval ... + sum(penalty_pv.*P_pv_curtail) * interval; ops = sdpsettings('solver', 'cplex', 'verbose', 2, 'showprogress', 1); sol = optimize(Constraints, objective, ops); if sol.problem ~= 0 error('第一阶段求解失败:%s', sol.info); end这里的interval是时段长度(小时),如果改成日内15分钟调度,只把T改成96、interval改成0.25即可。目标系数前面的乘法和interval统一起来很关键,成本单位是元/小时,如果不乘时间间隔,一天下来成本会明显算小,结果曲线看着正常但数值对不上账。
3.3 第二阶段实现与两阶段衔接
第二阶段的输入是第一阶段求得的最优解。我用value()函数把第一阶段变量存成基准值,再复制一份变量作为“实际值”,两者之差就是调整量。以下是第二阶段的核心建模片段:
% 读入第一阶段结果 P_MT_base = value(P_MT); P_pv_base = value(P_pv); Pch_base = value(Pch); Pdis_base = value(Pdis); % 第二阶段决策变量:实际出力 P_MT_act = sdpvar(1, T, 'full'); P_pv_act = sdpvar(1, T, 'full'); Pch_act = sdpvar(1, T, 'full'); Pdis_act = sdpvar(1, T, 'full'); % 调整量约束 Constraints2 = []; Constraints2 = [Constraints2, -delta_max <= P_MT_act - P_MT_base <= delta_max]; Constraints2 = [Constraints2, -delta_pv_max <= P_pv_act - P_pv_base <= delta_pv_max];第二阶段同样要保留功率平衡和电压约束,只是把出力变量替换成“实际值”。目标函数用绝对值表示调整量加权和,Yalmip处理绝对值很方便,直接写norm(A,1)或者加辅助变量转换都可。这里要注意的是,第一阶段已经算出来的值在第二阶段就应该当常数用,不要再声明成变量,否则两阶段就变成一个大优化问题,完全失去分层的意义。
3.4 求解器参数和优化技巧:别让模型卡死在整数变量上
两阶段模型里有不少二进制变量,比如储能充放电互斥变量、微型燃机的启停变量,这类混合整数规划问题用商业求解器都能跑,但参数不调容易“看着在算,实际半天不出结果”。我常用的参数配置是用sdpsettings设置求解器的MIP间隙和输出频率,比如限定MIP gap为0.1%,允许的求解时间上限设一个值,超出就取当前最优解。这样至少保证工程上有解可用,不至于开个会回来还在转圈。
缓存结果也是常用技巧:把第一阶段的解保存成.mat文件,第二阶段单独运行加载,省去每次调试都重新求解第一阶段的时间。实际项目中第一阶段混合整数规划的求解时间在几分钟量级,反复运行非常耽误事,缓存方案让整体调试效率提高明显。第二阶段由于连续变量占绝大多数,通常几秒内就能解完,整个两阶段跑完的时间和单阶段比也没有数量级的差别。
4. 算例测试与结果分析:数值说话的环节
4.1 测试系统与基础数据准备
代码里我用的是配电网领域常见的33节点标准测试系统,结构上是一个辐射状馈线网络,系统基准电压取10kV,馈线末端有光伏、风电和储能接入。基础数据从原始算例文件中读出网架阻抗和负荷基准值,再叠加自定义的负荷曲线、光伏出力曲线和风速曲线。这样的组合方式可以保证模型有通用性,换成其他网架只需要替换data目录下的阻抗和负荷参数。
这里有个容易忽略的细节:不同来源的数据量纲一定要归一化。负荷和DG出力我都换算成标幺值或统一到同一个功率基准,基准功率不统一的话,潮流约束很容易出现量级不匹配导致错误。数据文件做好单位标注,就算过了半年再回来改参数,也不会一头雾水。如果是从Excel导入数据,建议在Matlab里写一个read_data.m专门做单位转换和曲线插值,别在main里堆一堆数据处理代码。
4.2 两阶段模型的结果核心指标
以某个典型夏季日的测试为例,第一阶段优化得到的购电曲线在白天负荷高峰有明显抬升,微型燃机在晚间负荷爬坡时段出力增加,储能基本遵循“夜间充电、白天放电”的策略。这套结果符合对配电网的基本认知:能买便宜电的时候尽量买,高价时段靠储能和本地机组缓解,整体经济性逻辑在线。
第二阶段的修正量主要集中在光伏出力波动剧烈的时段,比如午后云层变化带来的出力跃变。调整量曲线显示,第二阶段通过调储能和微型燃机出力,把电压偏差控制在安全范围。对比单阶段模型加事后修正的做法,两阶段模型的综合运行成本低大约4%到6%,这个数值在不同负荷曲线下会有浮动,但趋势稳定。更重要的是,两阶段模型的电压越限次数明显减少,这才是这个框架在工程上真正值钱的地方。
4.3 灵敏度分析:光储比例和备用系数的影响
我在项目里还跑了若干组灵敏度测试,为的是回答“模型中哪些参数对结果最敏感”。固定其他条件不变,提高光伏渗透率后,弃光惩罚的作用开始显现:渗透率低于20%时几乎没有弃光;超过40%后,弃光比例快速上升,两阶段模型的优势也随之变小,因为不确定性越来越大,基准计划的参考价值下降。这个结论写进报告里很有说服力,也提醒我们不是所有配电网都适合盲目堆光伏。
备用系数直接影响第二阶段的可调整空间。太小了断电风险高,太大了储能和机组频繁偏离基准点,经济性变差。实测下来,备用系数取预测负荷的5%到10%是比较稳妥的范围,具体取值可以结合历史预测误差分布来定。这两个参数的灵敏度分析做完,模型的机理基本就摸清了,后面再调整参数也有依据可循。
5. 常见问题与排查技巧实录:都是踩过的坑
5.1 模型无解:先把约束列出来逐条检查
两阶段模型最常见的报错就是infeasible。我的排查顺序很固定:先看功率平衡约束是否有解,再看储能SOC的初末端相等约束是否和充放电功率矛盾,最后看二进制变量对应的整型约束有没有把可行域彻底锁死。多数无解问题出在前两条。举个例子,我一度把SOC末端值设置得过高,同时充电功率上限又偏小,结果就是无论怎么充都达不到目标SOC,整个模型直接无解。发现之后把末端SOC改为柔性约束(允许在一个区间内),问题立解。这类约束在工程上比硬约束更合理,因为储能不一定非要在调度周期末充满到给定值,允许一个小偏差能换来整体可行域的明显扩张。
5.2 Yalmip报NaN或维度错误
Yalmip建模中sdpvar矩阵维度不匹配是高频问题。比如T=24,SOC变量定义成1×(T+1),而循环里写SOC(t+1)==SOC(t)+...,如果t从0开始,第一个索引直接越界。此外,变量混用了行向量和列向量也会让约束矩阵错位,Yalmip有时不会立即报错,而是生成一个维度不对的约束,最后求解器返回NaN。
我的排查经验是:建模后先执行check(Constraints)查看约束是否全部为线性或凸,再用size()检查每个关键变量的维度。检查约束和变量的维度是最快的修复路径,不要等到求解器报了错再回头翻模型,那样往往要花好几倍时间。这里把我在调试中常遇到的报错整理成一个速查表,方便对照。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 求解器返回infeasible | 功率平衡或SOC约束冲突 | 改用柔性约束,放大边界 |
| Yalmip报NaN或维度错误 | sdpvar矩阵维度不匹配 | 用size()逐一检查变量维度 |
| 跑很久不出结果 | 二进制变量太多或MIP gap过紧 | 放宽MIP gap,提供初始可行解 |
| 第二阶段结果异常 | 基准值没有正确固定 | 用value()赋值并检查维度 |
5.3 求解太慢的几个提速操作
含二进制变量的混合整数规划在配电网规模不算大,但每一个二进制变量都会拖慢求解。提速的优先顺序是:减少二进制变量数量,比如对储能充放电,如果成本模型允许,可以去掉互斥变量、用惩罚辅助变量的方式替代;其次是给求解器设置合理的MIP间隙,从默认的1e-4放宽到1e-2,对工程精度基本无影响但速度成倍提升;最后是给二进制变量提供可行初始解,能让分支定界少走弯路。我这套代码在33节点系统下,第一阶段大约需要几十秒到几分钟,第二阶段由于全是连续变量,通常几秒内就能解完。如果换成更大的配电网,建议优先考虑把DistFlow约束线性化、剔除非必要整数变量,这两个措施对规模扩展的影响最明显。
最后说点个人习惯。每次改完模型,我都会保存一份对应版本的输出结果,复制一套改动记录放在代码目录旁边,写清楚这次改了哪个约束、为什么改。这套配电网两阶段模型前前后后迭代过好几个版本,靠这份记录,每次回来改参数都能快速定位到上下文,效率提升不是一点半点。如果你也打算把这个框架用到自己的项目里,建议先拿一个小型算例完整跑通两阶段交互,再逐步加入光伏、储能和更复杂的约束,框架稳定以后,很多东西都是往里面填参数的事。