多微网优化这两年特别热,电网侧在做区域协同调度,园区侧也在搞多个微电网之间的功率互济。但真上手做优化的人都知道,多微网模型比单微网复杂不少——变量多、约束多、目标之间还互相牵制,用传统数学规划工具碰非线性、非凸问题时常卡壳,更别说模型改一版就要重新推导一次。我自己在MATLAB里用粒子群算法(PSO)跑多微网优化模型,从建模、写目标函数到调参踩了不少坑,这篇文章把整套思路和关键代码逻辑梳理一遍,希望对正在做微电网调度、综合能源优化或者刚开始接触PSO的同行有参考价值。
1. 多微网优化模型:从问题定义到数学表达
1.1 为什么单微网模型不能直接套用到多微网
单个微网调度的本质是“在一个局部电网内,让发电、储能、负荷之间达到平衡,同时尽量省钱”。目标函数和约束都比较直观,变量维度也不高。但一旦把几个微网通过联络线连起来,问题性质就变了:每个微网既有自己的本地负荷和分布式电源,又可以和相邻微网交换功率,全网还需要满足整体功率平衡。
我用一个三个微网互联的案例来理解这件事。微网A装了两台柴油机组和一组储能,微网B主要是光伏加储能,微网C负荷偏大但燃气轮机容量较小。三个微网通过两条联络线相连,联络线有传输功率上限。这种情况下,如果还是把每个微网当作独立主体分别求解,就忽略了功率互济带来的经济性——比如光伏充足的B微网在中午时段可以把多余电力送给负荷重的C微网,避免C启动高成本机组。多微网优化真正要回答的问题是:所有决策变量一起怎么取值,整体运行成本最低。
1.2 目标函数怎么建:把经济性与惩罚项都写清楚
我在项目里采用的目标函数是全天总运行成本最小化,分成三块:
- 燃料成本:主要是柴油机组和燃气轮机的耗量成本,通常会拟合成二次函数形式,比如 f = a × P² + b × P + c,其中P是机组出力。
- 联络线购电成本:从外部大电网买电的费用,按分时电价计算;如果允许向大电网卖电,还会增加售电收益项。
- 储能与弃电惩罚:储能循环损耗不好精确建模,我在目标里加了一个按充放电量比例计算的折旧成本;光伏和风电如果必须弃掉,也加一个惩罚系数,防止优化结果为了省燃料费而大量弃新能源。
之前调整过的一个版本里,还加了“碳排放成本”,就是把机组出力的CO₂排放量乘碳价折算成费用。加进去以后优化路径会发生挺大变化,因为碳价直接改变了机组调度优先级。如果项目有双碳背景,建议一开始就把这个成本项写进目标函数,后面改模型要方便很多。
1.3 约束条件:功率平衡、联络线限制、储能SOC一个都不能漏
多微网模型的约束条件比单微网多一个维度,我梳理成三类:
功率平衡约束是每个微网在任意时刻的净功率为零。写数学表达就是:本地发电 + 外部购电 + 联络线输入 - 负荷 - 联络线输出 - 储能充电功率 = 0。这个约束一定要严格满足,否则“优化出来”的调度计划根本没法执行。
联络线功率约束包括两个内容:一是每条联络线传输功率不能超过允许上限;二是方向约束,功率只能按指定方向流动。有些模型允许双向流动,那还需要引入整数变量判断方向,就变成混合整数问题了。PSO本身处理连续变量比较顺手,所以我在早期简化模型里做了正向不可逆的假设,再把方向作为参数写进数据表里,等模型跑通后再扩展。
储能约束是另一个容易出问题的地方。包括SOC上下限、充放电功率上限,以及一个24小时周期的“始末SOC相等”约束——否则很容易出现电池在最后时段疯狂放电导致SOC跌到很低的情况。这个始末平衡约束非常重要,我见过很多人漏掉它,优化结果表面好看,实际完全不可用。
以我用的三微网模型为例,决策变量包括:每台机组在每个时段的出力、储能充放电功率、联络线功率、弃光弃风量。假设三个微网一共4台机组、3组储能、2条联络线,分24个时段,决策变量维度大概是(4+3+2)×24,再加上弃电变量,就超过260维。这还只是三个微网,规模一大,变量数轻松上千。这也是我选择PSO而不是穷举法或动态规划的原因——后面会详细说。
2. 粒子群算法求解多微网模型的底层逻辑与参数设计
2.1 为什么PSO能在高维非线性优化里站稳脚跟
粒子群算法最吸引我的一点是它对目标函数的“脾气”要求很低。线性规划需要约束矩阵是线性的,动态规划需要问题满足贝尔曼最优性原理,梯度类算法需要目标函数可导。而PSO只要求你能算出一个适应度值,不需要任何梯度信息。多微网模型里二次燃料成本、分时电价跳跃、SOC积分约束混在一起,目标函数大概率是非凸、非线性的,PSO这类群体智能算法反而能通过随机搜索找到满意解。
PSO的原理说起来也简单:每个粒子代表一个候选解,好比一群鸟在搜索空间里飞,每个个体记住自己找到过的最好位置(个体极值),群体共享目前发现的最好位置(全局极值)。下一次飞行时,粒子的速度由三个因素叠加:惯性保持原来的运动方向、向自己历史最优飞、向群体历史最优飞。在MATLAB里,这个更新公式的核心就是:
v(i,j) = w × v(i,j) + c1 × rand × (pbest(i,j) - x(i,j)) + c2 × rand × (gbest(j) - x(i,j))
x(i,j) = x(i,j) + v(i,j)
理解这个公式的物理意义比背代码更重要。w大,粒子探索范围广,适合前期全局搜索;w小,粒子局部细化能力强,适合后期收敛。c1和c2则代表“相信自己的经验”与“相信群体的消息”之间的权重。
2.2 参数设置:一组实用取值与调参优先级
参数设不好,PSO跑出来的结果会很飘。我经历过一轮轮试错,目前的经验值如下:
| 参数 | 推荐区间 | 说明 |
|---|---|---|
| 粒子数N | 30~80 | 决策变量维度越高,粒子数越要多。260维问题我常用60个粒子 |
| 迭代次数T | 100~300 | 先跑300次看收敛曲线,如果200次就平稳了,再降到200省时间 |
| 惯性权重w | 0.9线性降到0.4 | 前期大搜,后期细调,比固定0.7效果好很多 |
| 学习因子c1/c2 | 1.5~2.0,通常c1=c2=1.8或2.0 | 强调全局协作时c2略大于c1 |
| 速度上限vmax | 决策变量范围的10%~20% | 限制粒子飞出可行域和突变 |
调参优先级上,最敏感的是惯性权重策略,其次是粒子数,学习因子的影响相对温和。我经常看到有人一上来就调c1、c2,折腾半天没有明显变化,实际上先把w设成线性递减就解决了大半问题。
2.3 与遗传算法和传统优化方法的对比
做这个项目前,我也纠结过用遗传算法(GA)还是PSO。两者都适合非线性优化,但差别在实际使用感受上:GA有选择、交叉、变异多个算子,参数多,调起来牵一发动全身;PSO只有速度和位置两个更新式,概念上更好向非纯算法背景的工程人员解释。在同一个260维多微网模型上,我分别跑过GA工具箱和自写的PSO,同等迭代次数下,PSO收敛到的最优值通常比GA略好5%左右,计算耗时也少一截。
不过要说清楚,PSO和GA都属于近似算法,不能保证全局最优。如果你的项目精度要求极高,建议先用PSO搜索得到一个较好的初值,再把它作为初始点交给fmincon做局部精细化。这种“启发式全局搜索+梯度局部优化”的组合方式,我在另一个综合能源调度项目里试过,比单独用PSO提升约3%~8%的目标值,而且耗时增加可以接受。
3. MATLAB实现的关键细节:从数据组织到适应度函数
3.1 数据结构设计:让代码可读又可扩展
MATLAB里的一个常见问题是脚本越写越长,模型参数、算法参数、结果输出全搅在一起。我做多微网模型时的做法是用结构体(struct)组织输入数据,把问题定义和算法逻辑分开。
主程序里定义:
% 微网基础数据 ess.N = 3; % 微网数量 ess.horizon = 24; % 调度时段 ess.load = load_data(ess.N, ess.horizon); % 负荷矩阵 ess.pv = pv_data(ess.N, ess.horizon); % 光伏预测 ess.gen = gen_data(ess.N); % 机组参数(成本系数、出力上下限) ess.storage = storage_data(ess.N); % 储能参数(容量、SOC上下限、充放效率) ess.line = line_data(ess.N); % 联络线拓扑与功率上限这样做的直接好处是,改“微网数量”或“时段数”只需要动一个数字,数据读取、适应度函数、约束校验都能自动适配,不需要到处改。后面想扩展成带风电、可中断负荷、电动汽车充电桩的模型,也只需要往结构体里加字段。
3.2 适应度函数与约束处理:罚函数怎么选才不出问题
适应度函数是整个PSO的核心,写得好不好直接决定算法能不能收敛到合理区域。我的做法是:粒子解码成所有决策变量后,先做一次硬性约束检查,再计算目标函数值。但是对于功率平衡这类必须严格满足的约束,我用罚函数处理——违反量越大,惩罚越重。
常用罚函数形式是:
fit = obj + λ × Σ(max(0, g_ineq)) + μ × Σ|h_eq|
其中g_ineq是不等式约束的违反量,h_eq是等式约束违反量,λ和μ是惩罚系数。这里有个经验教训:惩罚系数不能一开始就设到极大值,否则目标函数值域被淹没,粒子完全靠着“满足约束”的方向走,优化等于没有优化。我一般先把λ设为目标函数量级的10~50倍,看收敛曲线的结果再调整。
另一种更稳妥的处理是“可行性优先修复”。对SOC越限、联络线功率越限这类可以直接截断的变量,解码时就直接把它拉回边界,这样大部分情况下粒子都在可行域内,罚函数只处理功率平衡这种全局耦合约束。我在项目里就是这个策略:局部变量修复,全局约束惩罚。
3.3 核心代码框架:一段可以直接跑通的PSO主循环
这里给出一个简化但逻辑完整的PSO主循环结构,适应度函数按前面目标函数的设计替换即可:
% 参数设置 nParticles = 60; nIter = 200; dim = nDecVar; % 决策变量维度 lb = lowerBound; % 变量下界向量 ub = upperBound; % 变量上界向量 w_max = 0.9; w_min = 0.4; c1 = 1.8; c2 = 1.8; % 初始化粒子位置与速度 x = repmat(lb, nParticles, 1) + rand(nParticles, dim) .* (ub-lb); v = 0.1 .* (ub-lb) .* rand(nParticles, dim); pbest_x = x; pbest_fit = inf(nParticles, 1); gbest_fit = inf; for iter = 1:nIter w = w_max - (w_max - w_min) * iter / nIter; % 线性递减惯性权重 for i = 1:nParticles fit_i = multiMicrogridObjective(x(i,:), ess); if fit_i < pbest_fit(i) pbest_fit(i) = fit_i; pbest_x(i,:) = x(i,:); end if fit_i < gbest_fit gbest_fit = fit_i; gbest_x = x(i,:); end end % 更新速度和位置 r1 = rand(nParticles, dim); r2 = rand(nParticles, dim); v = w .* v + c1 .* r1 .* (pbest_x - x) + c2 .* r2 .* (gbest_x - x); v = max(min(v, vmax), -vmax); x = x + v; x = max(min(x, ub), lb); % 边界截断 end这版代码里最需要注意的细节是:粒子位置更新后必须做边界截断,否则速度会越来越大,粒子飞到距离很远的地方,目标函数计算会出现Inf甚至NaN,收敛曲线断崖式崩溃。另一个细节是初始化时不要用零向量,而是在可行域内均匀随机撒点,这样粒子的多样性才能保证早期搜索覆盖尽可能多的区域。
3.4 适应度函数内部的计算效率优化
适应度函数会在整个算法里被调用几千次到几万次。如果内部用一层层for循环遍历所有时段和微网,耗时会非常可观。项目里我做过一次测试,把适应度函数从纯循环改成矩阵运算,同样的300次迭代直接从28秒降到9秒。
具体做法是把机组出力、储能功率这些矩阵一次构造成 [N×24] 的规模,再通过数组运算计算燃料成本、SOC递推和功率平衡。MATLAB矩阵运算比逐点循环快一到两个数量级,尤其在多微网这种维度高的模型里,这个优化不是锦上添花,而是能不能快速调参迭代的关键。调试时可以先写循环版本,确保逻辑正确,确认无误后改成向量化。
SOC递推虽然天然是个逐时段的过程(SOC(t+1) = SOC(t) + η_c × P_ch - P_dis / η_d,η是充放电效率),但也可以用cumsum函数一次性算完整条曲线。我第一次意识到cumsum能解决这个问题时,真是省了一大半改代码的时间。
4. 仿真结果分析与调参实战:那些文档里不会写的事
4.1 从收敛曲线判断模型与算法是否健康
跑完PSO以后,第一时间不是看最终成本数字,而是画出收敛曲线。一个健康的收敛过程是:前20~40代目标值快速下降,之后逐步变缓,最后趋于一条平坦横线。如果看到收敛曲线像锯齿一样上下乱跳,大概率是速度上限vmax设得过大;如果收敛曲线在第100代还在持续明显下降,说明迭代次数不够,粒子数或搜索能力还不足以稳定逼近最优区域。
我个人还习惯同时记录“群体平均适应度”和“全局最优适应度”。群体平均适应度反映整体搜索状态,如果平均值快速向最优值靠拢,说明种群多样性正在丧失;如果平均值一直远高于最优值,说明粒子分布太散,还没充分搜索。理想状态是两者同步下降,后期逐渐重合。
4.2 三个典型的调参失败场景与解决思路
失败的场景千奇百怪,但绝大多数能归结为三个类型。
第一种是罚函数系数过小,导致结果里功率平衡约束严重违反。判断方法简单:画出每个微网每个时段的净功率曲线,如果某些时段偏离0非常多,说明罚函数权重不够。解决方式是逐步增大λ,直到最大违反量降到总负荷的1%以内。
第二种是惯性权重衰减太快或太慢。w从0.9降到0.4一共200代,前100代w还大于0.65,粒子探索很活跃,后50代才逐渐细化。如果把w设成固定0.5,粒子早期探索不足,非常容易陷入局部最优,收敛曲线早早“躺平”。另一个极端是w衰减太慢,后期粒子还在大范围飞,最优区域内细挖不够,结果精度差。
第三种和储能SOC相关。如果模型中SOC始末相等的约束没写进罚函数,优化经常会把储能当作“免费电池”,让SOC在一天内反复充放,成本确实算低了,实际运维时根本不允许。我发现这类问题的方法是查看SOC曲线是否在上下边界频繁碰撞,正常调度SOC曲线应该比较平滑,有充电、放电、保持三种状态的合理切换。
4.3 随机性带来的坑:为什么两次运行结果不一样
PSO是随机算法,不设置随机种子的话,每次运行结果都有微小差别。这在研究阶段可以接受,但当你需要向甲方或导师汇报“最优成本是XX元”时,两次结果不一致会很尴尬。
我的做法是在主程序开头固定全局随机种子:
rng(2024); % 固定随机种子,确保实验可复现但要注意,固定随机种子只能保证在同样代码、同样参数的条件下复现。你改任何一个参数、换一台电脑、更新MATLAB版本,得到的结果可能都会变。更科学的做法是,对同一组参数跑10次独立实验,记录最好值、最差值、平均值和标准差。正常情况标准差应该在平均值的1%~3%以内,如果标准差偏大,说明粒子数太少或迭代次数不足,这时候不是追求一次跑得最好,而是提高算法稳定性。
4.4 怎么验证优化结果是真的“优”
这个问题常被忽略。PSO给出的结果只能说是“PSO认为的好解”,需要验证它是否真的合理。我建议拿一个小规模案例做交叉验证:比如把一个三微网24时段模型简化成2个微网2个时段,决策变量维度降到几十,然后分别用PSO和穷举法(决策变量按步长离散扫描)求解,看结果是否接近。如果两者相差在5%以内,说明PSO和适应度函数的实现没问题,可以放心让PSO跑大规模模型。
另一种思路是用MATLAB自带的fmincon作为交叉验证工具。把目标函数和约束写进fmincon的接口,从多个初始点出发求解,取最优结果和PSO对比。虽然fmincon可能卡在局部最优,但如果连局部最优都明显优于PSO结果,那说明PSO的参数或实现有严重问题,需要回头检查了。
5. 从实验室到工程项目:多微网PSO优化的进阶扩展经验
文章最后聊点工程化落地层面的经验。我最初把这个PSO跑通的场景是科研仿真,但后来和做园区能源管理系统的朋友交流,发现从“算法跑通”到“系统能用”之间还有很长的路,这里分享几个真实心得和扩展方向。
一是数据输入和结果输出要做成模块化。学术界常拿到的是理想负荷、光伏曲线,但工程现场数据有缺失、有噪声、有时间戳对齐问题。建议在MATLAB里写统一的dataLoad函数,从Excel或MAT文件读取数据,支持按天滚动。结果输出则要自动生成每个微网的功率曲线图、联络线功率图、SOC曲线图和机组出力的甘特图,方便直接截图汇报。
二是PSO本身可以做自适应改进。我跑过的一个变体是“自适应惯性权重”——根据群体适应度集的分布动态调整w。当粒子聚集在局部极值附近时,增大w帮助逃逸;当粒子分散时,减小w加强局部搜索。这个改进在标准PSO收敛效果不佳的问题实例上能带来明显提升,代码改动也不大,在原来线性递减w的位置换成几行自适应逻辑即可。
三是多目标扩展。实话说,单纯只优化经济成本的模型在现实汇报中说服力有限,因为业主通常还关心碳排放、新能源渗透率、储能循环次数等指标。PSO扩展多目标的标准做法是NSGA-II或MOPSO,MATLAB里也有现成的paretosearch函数可以调用,核心是改适应度函数为多目标输出,然后选出帕累托前沿。我在最终项目中把碳排放单独拎出来做了一个双目标版本,输出结果为帕累托前沿曲线,甲方可以直观看到“成本降低多少对应碳排放增加多少”的权衡关系。
四是结合时序滚动更新。多微网系统不是静态的,负荷和光伏都是时变数据。工程落地时一般做成滚动优化框架:每个调度周期开始前,用当前最新的预测数据重新运行PSO,得出未来24小时的调度计划,只执行前1~4小时,之后滚动更新。这个框架下,PSO单次求解速度就非常关键。我实测过,把MATLAB的PSO主循环部署在实时脚本里,60个粒子、200次迭代、24时段三微网模型,单次求解约10秒,完全满足小时级乃至分钟级滚动调度的实时性要求。
我在实际项目中的使用感受是:多微网优化本身不是PSO或某种算法的“专利”,但PSO的灵活性和MATLAB的快速原型能力结合得非常舒服。做研究阶段,用MATLAB和PSO把模型吃透,固然能快速出结果,但更重要的是通过调参过程真正理解多微网调度的物理约束。后续如果再遇到更大规模、更高复杂度的系统,这个从数学建模到算法落地的思路依然通用。