做微电网优化的朋友应该都有体会:单个微网的调度已经够折腾,要再把它放进微网群里,和其他几个微网协同优化,难度完全是另一个量级。前阵子我在整理一套用Matlab编写的微网群优化调度程序,核心就是实现三个微网的协调优化调度策略,今天把整体思路、建模细节、程序结构和实操中踩过的坑一次讲清楚。
这套程序解决的是真实工程里很常见的问题:三个地理位置接近、电气上相连的微网,分别有自己的光伏、风电、储能和负荷,如果各跑各的调度,很容易出现一个微网弃光、另一个微网却在高价买电的尴尬局面。群协调调度的作用,就是把三套独立系统放到同一个优化框架里,让功率在微网之间流动起来,从整体上降低运行成本、提高可再生能源消纳水平。
如果你正在做微电网方向的研究、毕业设计,或者刚到项目组需要接手多微网优化调度的代码,这篇内容应该能帮你绕开不少弯路。我会从问题建模开始,一路讲到Matlab程序怎么组织、关键参数怎么调、出了故障怎么排查,篇幅不短,但都是实测经验。
1. 微网群优化调度:你真正要解决的是什么问题
1.1 单微网到微网群的转变,问题复杂度在哪
单个微网优化调度,通常包含分布式电源(光伏、风电、燃气轮机或者柴油机)、储能系统、本地负荷,决策量无非是每台机组的出力、储能充放电功率、与上级电网的交换功率,目标一般是运行成本最低。模型本质上是一个混合整数线性规划(MILP)或者混合整数非线性规划,因为机组启停是0/1变量,储能SOC跨时段递推又把各时段耦在一起。
到了微网群,问题立刻变了。三个微网通过联络线连在一起之后,决策变量里多了一类“微网间传输功率”。不再是每个微网自己平衡就完事,而是要考虑:微网A光伏发多了,是应该弃掉,还是送给微网B供负荷?微网C的储能还有容量,是给本地留着,还是帮微网B消纳多余功率?这些问题的答案,单微网模型给不出来,必须显式建模微网间的功率交互。
复杂度还体现在约束数量上。假设每个微网有2台机组、1台储能、1条与上级电网的联络线,三个微网两两互联,那么每一时段要考虑的等式和不等式约束加起来少说也有四五十条。如果步长取24小时一天24个时段,连续变量加整数变量可以到上千个。用Matlab写这类程序,核心难点不是把模型堆出来,而是让程序结构清楚、求解效率可控、换一套数据或者换一个拓扑能快速复用。
1.2 优化调度的目标函数与约束条件,不只是省钱
很多人刚上手时,脑子里只有“成本最小化”这一件事。实际做微网群调度,目标函数往往是多重的,例如:
- 运行成本最小:燃料成本、购电成本、设备维护成本,再减掉售电收益。
- 可再生能源消纳率最高:尽量减少弃风弃光,尤其在“绿电”示范项目里,这个指标可能比成本还重要。
- 碳排放量最小:部分园区项目有碳排放额度约束。
- 与上级电网交换功率的波动最小:平滑联络线功率曲线,对电网友好。
程序里可以把几个目标分别带上权重组成一个单目标,也可以分优先级做字典序优化。但我强烈建议:第一版程序先只做运行成本最小,把其他目标作为软约束罚项放进去。原因是多目标权重如果设置不当,优化器会为了降低罚项做出非常不合理的调度策略,而你很难在第一时间发现。
约束条件方面,除了每个微网内部的功率平衡、机组出力上下限、爬坡约束、储能SOC约束、机组启停逻辑,还要额外加入微网间的传输功率约束。比如两个微网之间的交流联络线容量有限,功率方向不能随意反转;再比如三个微网都是并网运行,公共连接点处的总购电功率不能超过变压器容量。如果这些约束漏掉一条,结果就会看起来完美,实际用不了。
这里特别提醒一点:如果是毕业设计或者项目Demo,网络潮流模型不要一开始就上交流潮流的非线性版本。多数情况下用直流潮流近似已经足够刻画功率分配关系,计算速度快,还能直接套用线性规划和混合整数规划求解器。数学上,功率平衡方程从复数域变成实数域线性方程,问题规模瞬间小了很多。
2. Matlab程序整体架构与核心模块拆解
2.1 程序输入:基础数据、负荷曲线、光伏/风电出力、电价
我写这类程序时,输入数据全部用Excel统一管理,而不是硬编码在脚本里。原因是优化调度程序要反复试算不同场景:比如夏天和冬天的负荷曲线不一样,光伏大出力和小出力日不一样,分时电价方案会调整。数据文件化管理,改一个场景只需要改文件,不用动代码。
典型输入数据包括:
- 各微网的分布式电源参数:机组台数、额定功率、燃料成本系数、启动成本、爬坡率、最小启停时间。
- 储能参数:电池容量、最大充放电功率、初始SOC、SOC上下限、充放电效率。
- 联络线参数:相邻微网间交换功率上限,可选的线路损耗系数。
- 预测曲线:未来24小时(或96点)的负荷预测、光伏预测、风电预测数据。
- 电价曲线:分时购电价、上网电价,部分场景还有需求响应激励电价。
这些数据里最容易犯的错是量纲不统一。功率用kW,能量用kWh,电价用元/kWh,成本用元,SOC是0到1的小数,如果某个字段从MW输入、另一个从kW输入,最后优化结果会很离谱,而且很难排查。我一般在主程序开头写一个统一的单位换算函数,把所有量纲提前转成kW/kWh/元体系,后面所有模型都基于同一套单位写。
2.2 优化求解流程:从数据预处理到结果输出
整体流程用伪代码看非常清晰:
% 数据初始化 [Load, PV, Wind, Tariff, Param] = loadInputData('case3mg.xlsx'); % 生成决策变量(以Yalmip为例) x = sdpvar(nVar, nPeriod, 'full'); % 连续变量,比如机组出力和交互功率 u = binvar(nUnit, nPeriod, 'full'); % 整数变量,比如机组启停标志 % 生成约束 Constraints = []; Constraints = [Constraints, powerBalance(mg, x, u, Load, PV, Wind, Tariff)]; Constraints = [Constraints, storageSOC(SOC, P_ch, P_dis, Param)]; Constraints = [Constraints, tieLineLimit(P_tie, Param)]; % 目标函数 Objective = sum(operationCost(x, u, Tariff, Param)) + sum(penalty(RenewableCur)); % 也可以把弃光罚项、SOC偏差罚项一并放进Objective % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 2, 'gurobi.TimeLimit', 300, 'gurobi.MIPGap', 0.01); optimize(Constraints, Objective, ops); % 后处理 plotDispatchCurve(result); exportResultToExcel(result);我用Yalmip作为建模层,求解器用Gurobi或Cplex。Yalmip会把sdpvar形式的变量和约束转成求解器标准形式,省去手动拼矩阵的巨大工作量。如果不想装商业求解器,也可以用Matlab自带的intlinprog,但三个微网两两互联、24时段、带机组启停整数变量时,intlinprog的速度和稳定性会明显吃紧,调试模型时还好,做大量场景对比时会很想哭。
主程序我一向分成四个层级:数据层、模型层、求解层、结果层。数据层只管读文件,模型层只生成约束和目标,求解层调用求解器,结果层画图和导出。这样以后改拓扑结构,比如把三个微网的星形接线改成两两互联,只需要改模型层里的联络线约束,其他三层完全不动。
3. 三个微网的协调调度策略核心细节
3.1 独立调度与群调度的差异,功率交互建模
这里要先分清楚:你写的是“三个独立调度拼在一起”,还是“真正的微网群联合优化”。很多网上流传的代码,其实是分别跑三遍单微网优化,再把结果拼起来,这种根本不能叫微网群优化。
真正的群优化,决策变量里必须有联络线功率,并且每个微网的功率平衡方程要包含这条联络线。简化版平衡公式如下:
对微网i、时段t:
光伏出力 + 风电出力 + 机组出力 + 储能放电 + 从上级电网购电 + 从其他微网受入功率 = 本地负荷 + 储能充电 + 向上级电网售电 + 向其他微网送出功率
如果三个微网接到同一段公共母线,也就是星形结构,还可以再加一条群级约束:任意时段,所有微网的净交互功率之和为零。这条约束等于把三个微网绑成了一个整体,功率富余的微网自然会把电量送给缺电的微网。
程序里常见两种互联方式:
- 两两互联:微网A-B、B-C、C-A各有一条联络线。每条线两端的交换功率要相等,并且受线路容量限制。这种情况变量多,约束多,适合做更精细的网架分析。
- 星形结构:三个微网都接公共母线。每一路交互功率变量变成一个净功率,公共母线约束写sum(P_tie) = 0,再对每个微网的交互功率加绝对值上限即可。
我建议先做星形结构。它最直观、易实现、变量少,适合把整个调度策略跑通。等完全理解了群优化的意义,再改成两两互联也不迟。
3.2 储能系统充放电策略与SOC约束
储能是多微网优化里最容易翻车的地方,特别是SOC跨时段更新公式,方向一写反,整个调度结果全是乱的。正确的递推式是:
SOC(i, t+1) = SOC(i, t) - P_ch(i, t) * eta_ch * dt / Cap + P_dis(i, t) * dt / (Cap * eta_dis)
注意充电时SOC上升,放电时SOC下降。eta_ch和eta_dis都要取小于1的数,放电侧放在分母上是因为放电过程有能量损耗。很多初学者把两个效率都放在分子上,结果SOC越算越低或者越算越高,调问题毫无头绪。
还要防止同一时段“边充边放”。数学上这种情况在纯LP里完全可能发生,因为成本目标会想办法制造一个充放循环来套利,但现实中电池不会同一秒又充电又放电。Yalmip里解决办法是引入二进制互斥变量:
u_ch = binvar(nMG, nPeriod, 'full'); u_dis = binvar(nMG, nPeriod, 'full'); Constraints = [Constraints, P_ch <= M * u_ch]; Constraints = [Constraints, P_dis <= M * u_dis]; Constraints = [Constraints, u_ch + u_dis <= 1];M取一个足够大的数,比如储能额定功率的5倍。这样增加了一组整数变量,但换来的是结果在物理上可解释。
SOC初值和终值也很容易出问题。如果程序不约束最终SOC,优化器会让储能在最后一个时段拼命放电,账面上收益很高,实际上第二天没法继续跑。我一般加一个约束,让SOC_end不低于SOC_init减去一个很小的偏差值,或者干脆要求SOC_end = SOC_init。后者更严格,适合做多日连续调度的前一日底稿。
3.3 目标函数与约束条件的数学表达
一个典型的三微网群日运行成本最小化问题,目标函数写成:
min Σᵢ Σₜ [ 燃料成本(i,t) + 运维成本(i,t) + 购电成本(i,t) - 售电收益(i,t) + 机组启动成本(i,t) ]
可再生消纳要求可以转成软约束。比如要求弃光率低于5%,就在目标函数里加一个很高的弃电罚项。罚项系数我习惯取正常电价的5到10倍,这样优化器会优先把弃光降下来,但不至于为了消纳最后0.01度电而让成本爆炸。罚项太小等于没有,罚项太大又会让原本合理的弃电消失,所以这个系数需要根据实际场景调。
约束集合里最核心的有五组:
- 每个微网各时段的功率平衡等式。
- 分布式电源出力上下限和爬坡约束。
- 储能SOC递推、SOC上下限、充放电功率上下限、充放电互斥。
- 微网间交接功率约束:总量平衡和线路容量。
- 群与上级电网的交互约束,比如总购电上限。
需要留意的是,Yalmip里用sdpvar变量写约束时,不要用Matlab等号赋值,要用“==”作为约束关系。而且变量是矩阵,维度很容易错。我的习惯是把所有数据统一成行为24、列为微网数的矩阵,在循环里逐时段添加约束,避免大规模的向量化操作带来隐蔽的维度Bug。
4. 实操记录:运行Matlab程序的关键步骤与参数调整
4.1 主程序入口与函数调用关系
我给这套程序定义的入口文件是run_multi_mg_optimization.m,调用结构如下:
cfg = init_config(); % 读配置文件,设置路径和求解器 data = load_data(cfg); % 读Excel数据 [model, var] = build_model(data); % 生成变量和约束 result = solve_model(model, cfg); % 调用求解器 report = post_process(result); % 画图和导出统计表 save_result(report, cfg.outDir);实际跑的时候,先用一个小算例验证模型。最常见的方法是临时把总时段数从24改到4,三个微网各只留1台机组,关掉启动成本,甚至可以先不考虑微网间功率交换。如果这样一个小模型都跑不出合理结果,说明约束组里肯定有逻辑错误,这时候去Debug,比在完整模型里大海捞针容易得多。
我当年调试的时候,就因为SOC递推公式里效率位置不对,在24时段全模型里查了整整一下午。后来学聪明了,固定一个小算例,把每个时段的SOC和功率都打印出来,对照手算看一遍,问题五分钟就定位了。
4.2 关键参数设置:求解器、时段数、权重
求解器设置一定要显式写明,不要用Yalmip默认配置。我的常用配置是:
ops = sdsettings('solver', 'gurobi', 'verbose', 2, ... 'gurobi.TimeLimit', 300, ... 'gurobi.MIPGap', 0.01, ... 'showprogress', 1);TimeLimit和MIPGap是两个必须设置的参数。三个微网、24时段、带启停整数变量,模型规模大概有上百个整数变量和几千个连续变量。默认参数下,Gurobi一般能跑完,但你加上弃光罚项、储能互斥变量之后,分支定界的搜索树会有爆炸趋势。不设时间上限,一串毕业论文实验可能跑到天亮都出不来。MIPGap设为0.01表示允许1%的次优性,工程里足够用了。
时段数方面,第一版我坚持用24点,也就是每小时一个断面。有了稳定结果之后,再把光伏和负荷曲线插值成15分钟一个点、即96时段,这会显著增加求解时间和内存,但对调度策略的刻画也更精细。不要一上来就96点,否则你连模型对不对都看不出。
还有一个参数是联络线功率是否允许双向流动。如果一些工程场景里两个微网签订了单向供电协议,交互功率变量的下界就要改成0,上界是传输容量。放开双向流动通常经济性更好,但需要更多控制保护配合。程序里我一般默认是[-limit, limit],具体场景再改。
4.3 结果分析:如何读懂调度曲线和收益表格
程序跑完之后,我会输出三张图:
- 各微网24小时的电功率平衡图,用堆叠柱状图显示负荷、光伏、风电、储能充放电、联络线功率。
- 三个储能SOC曲线,放在同一张图里对比。
- 群总体经济性统计表,包含各微网成本拆分、购电量、售电量、微网间交换电量。
看结果的顺序也有讲究。先看SOC曲线:有没有越限?最后有没有突然跌到0?再看功率平衡图:每个时段功率是否能闭合?最后看经济性表:和直觉对不对得上?
我特别提醒检查微网间交互功率的方向。星形结构下,虽然sum(P_tie) = 0保证净功率为零,但依然可能出现这种情况:A向公共母线送电,B同时从公共母线受电,C又从公共母线受电,而A从上级电网购电来补足自己送出的电量。这在经济上可能是最优的,但物理上成了“中转站”,损耗变大,调度策略也不好看。如果项目要求不能有这种无效中转,就得在模型里再加限制,比如规定每个微网某一时段要么只送、要么只受。
经济性表导出成Excel,我会包含这几列:总运行成本、各微网燃料成本、购电成本、售电收益、储能折算损耗、可再生能源消纳率、微网间交换电量净额。答辩或写项目报告时,这张表是直接可用的素材。
5. 常见问题与排查技巧实录
5.1 求解器报“infeasible”或者“不收敛”
这是多微网优化程序最容易遇上的问题,九成情况不是求解器坏了,而是约束之间互相矛盾。快速定位方法:
首先,把目标函数设为零,只求解可行性问题。如果这样依然不可行,说明约束集合有硬冲突,不是因为目标函数里的罚项系数异常。
然后,逐个微网手工检查功率平衡。最典型的场景是:某个微网负荷特别低、机组最小技术出力偏高、储能充电功率上限又不足,造成多余功率无法消纳,平衡等式无解。解决办法是把机组最小出力调低、增大储能充电功率,或者放开该微网向其他微网的输送限制。
最后,检查SOC递推约束。如果初值SOC是80%,终值要求90%,而储能充电功率上限和时长限制下根本充不到那个值,优化器必然报不可行。这种问题看约束参数一眼就能看穿。
5.2 储能出现同时充电和放电
即便写了互斥约束,偶尔还是会在结果里看到某时段充放电功率同时不为0。Yalmip对约束的默认容差很小,当数值解在边界上出现微小正负时,可能出现“伪同充放”。我的处理办法是:在后处理脚本里加一个阈值,把绝对值小于0.01kW的充放电功率直接清零,同时把相应的SOC按实际有物理意义的功率重新推算一遍,保证曲线自洽。
如果互斥约束压根没写,那就一定会出现同充放。优化器会在电价低谷时段充电、高峰时段放电,同时再用一个“充电再多卖一点电”的操作来套利,结果目标函数很好看,现场却做不出来。
5.3 数据维度不一致导致报错
这是Matlab程序里最让人抓狂的低级错误。某个数据文件里负荷是24行1列,另一个文件里电价是1行24列,模型里一减就出现维度不匹配。规范做法是从Excel读出来之后,统一转成列向量,比如[nPeriod, 1],然后在所有函数里维持同一维度。我习惯把所有时段数据都放在第一维,所有微网编号放在第二维,即[nPeriod, nMG]。这样在循环里写约束时,永远是i=1:nPeriod、j=1:nMG,不会错。
防止维度错误还有一个技巧:在数据预处理模块末尾加一行assert,检查所有矩阵的size是否与预期一致。比如:
assert(size(PV, 1) == nPeriod && size(PV, 2) == nMG, '维度错误:PV矩阵尺寸不对');这行代码平时看着多余,但能帮你省掉无数个小时的排查时间。
5.4 求解时间过长,如何优化
一般分三步:先砍模型规模,把24时段改成8点或12点,跑通验证逻辑;再优化代码写法,把循环约束改成矩阵向量化,Yalmip对向量化约束的处理速度远快于for循环;最后调整求解器参数,收紧TimeLimit,放宽MIPGap。
如果这些都做了还是慢,就要检查是不是有非线性的约束混进来了。比如用了乘积形式的P_ch*eta_ch,而eta_ch本身不是常数,就会把MILP变成MINLP,求解时间呈指数增长。我的原则是:第一版绝不混入非线性项,所有效率取常数,所有成本用线性函数,这样做出来的结果已经足够工程使用。
我把高频问题整理成一张速查表:
| 现象 | 可能原因 | 排查/解决 |
|---|---|---|
| 求解不可行 | 功率平衡约束冲突 | 零目标求解可行性,逐约束放松定位 |
| SOC曲线异常 | 储能效率方向写反 | 检查SOC递推公式中充放电效率的位置 |
| 微网间出现无效中转 | 缺少交互方向约束 | 根据拓扑增加净功率方向限制 |
| 目标值高得离谱 | 罚项系数过大或电价量纲错 | 统一量纲,检查电价和功率单位 |
| 结果里完全不本地发电 | 购电价低于发电成本 | 增加机组最小运行时间约束 |
| 变量太多内存不足 | 模型构建方式低效 | 向量化约束,减少临时变量 |
5.5 一个独家避坑技巧:先跑“零交互”基准场景
正式开始微网群协调调度之前,先把所有联络线功率锁死为0,也就是跑一个三套微网完全独立优化的基准场景。然后放开联络线功率,再跑一次群协调调度。对比两次结果的总成本,你会发现协调优化带来的收益是实打实的数字。
如果放开交互之后成本反而更高,那八成是模型约束写错了,比如某个手滑把联络线功率方向写反,让功率从高电价微网送去了低电价微网。把基准场景和群协调场景放一起看,几乎一眼就能定位。这个方法我在所有微网群项目里都会用,强烈推荐。
6. 扩展思路:从三个微网到更大规模微网群
6.1 双层优化、分布式一致性算法等方向
当前程序是集中式优化,所有微网数据汇总到一个控制中心建模求解。优点是全局最优、实现简单,缺点是当微网数量扩展到十个以上,或者不同微网属于不同运营主体,集中式模型的隐私顾虑和计算量都会成为瓶颈。
后续扩展有两个主流方向:
- 双层优化:上层解决群间功率分配和结算电价,下层各微网基于给定电价独立调度。上下层之间通过迭代收敛。这个方向适合做电力市场环境下的微网群交易。
- 分布式优化:用ADMM(交替方向乘子法)或者一致性算法,把集中式问题分解成各微网子问题,微网之间只交换边界交互功率变量,不泄露内部数据。改造的思路是:把当前集中式约束按微网分块,联络线功率作为耦合变量,用ADMM迭代更新拉格朗日乘子。每个微网内部依然可以复用现有子模型,只是交互功率从固定参数变成迭代变量。
这两个方向在Matlab里都有成熟案例可以参考。如果只是做课程设计,集中式已经足够;如果要发论文或者做实际系统落地,分布式优化才是更有价值的方向。
6.2 程序改造建议:模块化、可配置、可复用
如果要把程序用于不同拓扑场景,建议把约束生成函数改造成可配置形式。比如定义一个拓扑结构描述变量,是星形还是两两互联,程序根据这个描述动态生成对应约束。这样做的好处是一次建模,多处复用。
function [Constraints, vars] = buildTopologyConstraints(vars, topo) % topo = struct('type', 'star', 'numMG', 3); % 或者 topo = struct('type', 'mesh', 'lines', [1 2; 2 3; 3 1]); % 函数内部根据type生成不同的交互约束还可以把确定性优化升级成鲁棒优化或者随机优化,把光伏和负荷预测的不确定性纳入模型。这个方向的工作量和难度都会上一个台阶,但如果论文需要创新点,这是一个很值得投入的方向。
我个人在实际操作中的体会是:这套程序跑通后,不要急着写报告。先试着把三个微网的负荷曲线整体平移两个小时,观察调度策略是否会跟着合理变化。如果结果完全不变,或者变化方向不合理,说明模型可能过拟合到某一条具体曲线,甚至某些约束写死了市场行为。这种敏感性测试,比任何炫技都能更快暴露隐藏Bug。
如果你手头正好也在做类似的多微网优化项目,可以试着按这个思路把程序拆成数据、模型、求解、结果四层,再从头写一遍。别觉得重构麻烦,我保证第二次写出来的程序,会比第一版好用不止一倍。