中午十二点,光伏满发,配电网末端电压冲到1.08 pu,逆变器一片一片地跳闸保护——这是我去年帮朋友调一套分布式光伏并网仿真时遇到的截图场景,也是很多配电网工程师见过无数次的现实。要解决这类电压越限,单纯靠变电站变压器挡位调整是转不过来的,更有效的思路是把网络划分成多个集群,再配合集群内部的自治和集群间的协同控制。这篇文章想把“分布式光伏、配电网、集群划分、集群电压协调控制”这条链路的Matlab实现讲清楚,从原理到代码细节都有。适合正在做配电网研究、光伏并网课题的研究生,以及想快速搭一套仿真框架的工程师参考。
1. 为什么配电网集群划分越来越绕不开
1.1 分布式光伏接入后,电压问题到底是怎么来的
传统配电网是“树状辐射结构”,潮流永远从变电站往负荷末端单向流动,电压沿馈线一路往下掉,这也是为什么调度员习惯用“沿馈线电压递减”来判断网络是否正常。分布式光伏一接入,这条假设就被打破了。
光伏出力本质上是向节点注入有功功率。当某节点的光伏出力大于本地负荷时,多余的功率会沿馈线向上游倒送,这就产生了所谓的“逆潮流”。逆潮流最直接的影响就是电压抬升。工程上常用的电压降落估算公式是:
ΔU ≈ (PR + QX) / U
注意这里的P是沿该段馈线输送的有功,当功率方向反了,P就变成负值,ΔU符号翻转,末端电压不仅不掉,反而会被顶上去。如果此时刚好是大晴天中午、负荷又很轻,光伏满发而本地吃不下,电压越限几乎必然发生。我的仿真里就是在午间轻载场景下看到电压冲到1.08 pu,逆变器为了保护自身直流侧和交流侧设备开始连锁跳闸,越跳电压越不受控,最后整个馈线的电能质量都很差。
这种电压问题还有几个明显特点:一是局部性强,往往集中在离光伏接入点越近的节点越严重;二是波动性强,云飘过来遮住太阳,出力几分钟内可能掉一半,电压跟着剧烈波动;三是双向性,光伏不出力时电压有可能会偏低,尤其夜间重负荷场景。单纯靠变电站里的有载调压变压器(OLTC)去扛,动作速度慢、寿命有限,而且它只能整体抬升或降低馈线电压,无法解决局部电压过高的问题。这个时候就需要更细粒度的电压管理手段。
1.2 集群划分到底解决什么问题
如果把电压控制做到每个节点独立自治,理论上很完美,但实际不可行。配电网节点数量动辄成百上千,每个节点都装控制器、每个控制器都要和全局通信,通信成本和协调复杂度都爆表了;而且配电网拓扑经常变化,某条馈线检修,全网协调策略就要重算一遍,鲁棒性也很差。
“集群划分”的思路完全不一样。它把配电网按电气耦合强弱切成若干个集群,每个集群内部的节点在电气上“绑得比较紧”,集群与集群之间的耦合相对较弱。切完之后做一套分层控制:集群内部优先自治,内部资源用不完了再找邻居集群协调。这就像一个城市的网格化管理,每个网格的事情网格员先处理,处理不了再上报协调,而不是全市所有问题都由市政府直接管到每一条小巷。
集群划分在分布式光伏配电网里尤其重要,核心原因在于无功电压控制是强局部性的。对某个节点电压影响最大的,往往就是它附近那几个节点的无功调节。如果能把联系紧密的节点划到同一个集群,集群内基于灵敏度优先序做无功协调,效果会比“全网一把抓”的方案好得多,计算量小一个量级,控制速度也能满足配电网秒级响应的需求。
1.3 为什么选Matlab来做这套实现
我做这套东西之前也纠结过用Simulink还是纯M脚本,后来还是选了纯脚本。原因很直接:集群划分本质上是一个图论和矩阵运算问题,Matlab在矩阵运算上天然顺手;灵敏度矩阵、拉普拉斯矩阵、特征分解这些核心计算,Matlab都有内置函数。Simulink适合做电磁暂态和详细控制器建模,但集群划分这种算法研究,用M代码更透明、更容易调试,也更容易和论文里的公式一一对应。
版本方面,很多同学纠结R2023b还是R2025b,实测下来跑这套代码2020b以上都够了。核心工具箱就是MATLAB自带的矩阵运算、统计工具箱的kmeans函数,以及图处理的一些基础函数。真正关键的从来不是版本,而是代码里算法逻辑的清晰程度。
2. 从潮流计算到电压灵敏度矩阵,把基础打扎实
2.1 配电网潮流计算:用牛拉法还是前推回代法
做集群划分和电压控制,第一步是潮流计算,这一步选型很关键,因为它直接影响后续灵敏度分析和控制策略的实现。
配电网领域最经典的是前推回代法,原理简单、迭代速度快,特别适合纯辐射状网络。但它的缺点也很明显:处理多电源接入、环网结构以及PV节点时比较麻烦,每次迭代都要反复做前推和后代,不太容易直接提取雅可比矩阵和灵敏度信息。而集群划分和控制恰恰需要这些信息,所以我选择用牛顿-拉夫逊法。
牛拉法在输电网用得最多,在配电网用需要稍微注意收敛性问题,但好处是矩阵结构完整,一次计算就能得到雅可比矩阵的各分块,后面推导电压-无功灵敏度矩阵非常自然。两种方法的取舍,我给一个直观对比:
| 对比项 | 前推回代法 | 牛顿-拉夫逊法 |
|---|---|---|
| 收敛速度 | 线性收敛,快但迭代次数多 | 二次收敛,初值好时迭代次数少 |
| 环网支持 | 不好处理 | 天然支持 |
| PV节点 | 需要额外处理 | 直接支持 |
| 灵敏度矩阵获取 | 需要额外推导 | 解完潮流即有雅可比矩阵 |
| 编程复杂度 | 低 | 中高 |
| 集群划分场景适用性 | 一般 | 更合适 |
实际算例我用的是IEEE 33节点配电系统,基准电压12.66 kV,总负荷约3.7 MW + 2.3 Mvar,节点1是变电站出口的平衡节点。这套系统规模不大不小,既能体现集群划分的效果,又不会让调试时矩阵爆炸。
2.2 电压-无功灵敏度矩阵的推导与作用
集群电压协调控制的核心不是调节本身,而是“知道往哪个方向调、调多少最有效”,这全靠电压-无功灵敏度矩阵。
极坐标牛拉法的修正方程写出来是这样的:
[ ΔP ] [ J1 J2 ] [ Δθ ] [ ΔQ ] = [ J3 J4 ] [ ΔV ]
其中J1 = ∂P/∂θ、J2 = ∂P/∂V、J3 = ∂Q/∂θ、J4 = ∂Q/∂V。这里ΔV用的是电压幅值变化量而非标幺化的ΔV/V,是为了后面灵敏度矩阵直接对应每单位无功变化带来的电压变化,物理意义更直观。
分析电压-无功关系时,一般认为有功和无功的耦合是次要的。近似的做法是令ΔP = 0,把Δθ消掉:
Δθ = -J1⁻¹ J2 ΔV
然后代入无功方程:
ΔQ = (J4 - J3 J1⁻¹ J2) ΔV
所以电压-无功灵敏度矩阵就是:
S_VQ = (J4 - J3 J1⁻¹ J2)⁻¹
这个S_VQ的第i行第j列元素,物理含义是“节点j注入单位无功功率后,节点i电压的变化量”。正数表示无功增加电压升高,负数表示无功增加电压降低。对配电网来说,S_VQ一般在0.01到0.1的范围内,意思是注入1 Mvar无功能让附近节点电压提升1%到10%。
有了S_VQ,后面很多事情都顺了:哪里电压越限,找对应行,看哪个节点调节无功对它影响最大;集群划分也能用S_VQ构造电气距离;控制策略里还可以直接按灵敏度大小给逆变器排序。所以这一步是整个代码的“地基”。
2.3 Matlab里牛拉法的核心代码骨架
这部分代码我贴的是最基本、但绝对能跑的骨架。核心就是三件事:算不平衡量、组装雅可比矩阵、解修正方程更新状态量。
function [Vmag, theta, iter] = NRLF(Ybus, Sbus, V0, tol, maxiter) % NRLF 牛顿-拉夫逊法潮流 % Ybus: 节点导纳矩阵 % Sbus: 节点净注入复功率(发电机-负荷) % V0: 电压初值(相量列向量) n = length(V0); V = V0; theta = angle(V0); Vmag = abs(V0); for iter = 1:maxiter Vphasor = Vmag .* exp(1i*theta); S_calc = Vphasor .* conj(Ybus * Vphasor); dP = real(Sbus - S_calc); dQ = imag(Sbus - S_calc); % 这里把第一个节点设为平衡节点,应满足PV % 不同系统需要根据节点类型调整dP、dQ的维度 [J1, J2, J3, J4] = Jacobian(Ybus, Vmag, theta, S_calc); J = [J1, J2; J3, J4]; dU = -J \ [dP; dQ]; dtheta = dU(1:n-1); dV = dU(n:end); theta(2:end) = theta(2:end) + dtheta; Vmag(2:end) = Vmag(2:end) + dV; if max(abs(dU)) < tol break; end end end雅可比矩阵的组装公式比较长,我直接给解析式,方便你照着写循环。对i≠j:
J1(i,j) = Vi·Vj·(Gij·sin(θi-θj) - Bij·cos(θi-θj)) J2(i,j) = Vi·(Gij·cos(θi-θj) + Bij·sin(θi-θj)) J3(i,j) = -Vi·Vj·(Gij·cos(θi-θj) + Bij·sin(θi-θj)) J4(i,j) = Vi·(Gij·sin(θi-θj) - Bij·cos(θi-θj))
对角元稍微复杂一点,可以根据“P、Q从S_calc里取出来”直接构造:
J1(i,i) = -Qi - Bii·Vi² J2(i,i) = Pi/Vi + Gii·Vi J3(i,i) = Pi - Gii·Vi² J4(i,i) = Qi/Vi - Bii·Vi
注意公式里Pi、Qi是节点i的计算注入功率(从S_calc取),不是给定功率。写代码时建议把对角元和非对角元分两个循环,先算非对角,再补对角,这样不容易出错。我早期有几次就是对角元符号搞反,导致潮流振荡不收敛,排查了半天。
3. 基于电气距离的集群划分,让电网切得准
3.1 电气距离矩阵的计算:为什么不用地理距离
集群划分的第一个关键问题是:用什么衡量节点之间的“远近”。最直观的想法是地理距离,两条馈线挨得近就算一个集群,但这是错的。电气上两个节点即使地理位置隔了很远,如果中间有强联络线、阻抗很小,它们的电压和无功也会紧密耦合;反过来,地理上近但分属不同馈线、中间隔了变压器或长线路,电气耦合反而很弱。
所以工程上普遍用“电气距离”来度量。我采用的是基于阻抗矩阵的定义:
Dij = Zii + Zjj - 2Zij
其中Z是节点阻抗矩阵,也就是导纳矩阵求逆得到的Zbus。这个公式本质上计算的是两个节点之间的“有效电阻”——在纯电阻网络中它就是等效电阻,在配电网这种以阻抗为参数的网络中,它能很好地反映两点间电气耦合强弱:Dij越小,两节点电气上越近,无功调节越容易互相影响。
Matlab里算这个特别简单,就是两步:
Zbus = inv(Ybus); % 实际建议用 Ybus \ eye(n) n = size(Zbus, 1); ED = zeros(n, n); for i = 1:n for j = 1:n ED(i,j) = Zbus(i,i) + Zbus(j,j) - 2*Zbus(i,j); end end有了电气距离矩阵ED,就可以构造聚类算法需要的相似度矩阵。这里我习惯用负指数变换:
W_ij = exp(-ED(i,j)² / (2σ²))
σ取所有电气距离的标准差,这样距离近的节点相似度接近1,距离远的相似度迅速衰减到0附近。这个W就是后面谱聚类的输入。
3.2 谱聚类划分:完整流程和Matlab实现
集群划分的方法不少,模块度最大化、遗传算法、层次聚类都有人用。我用的是谱聚类,原因很实际:它天然处理图划分问题,不像k-means那样对数据形状敏感,而且配电网拓扑就是一张图,谱聚类对“集群内部紧密、集群之间稀疏”的目标拟合得非常好。
谱聚类流程可以拆成五步:
- 根据相似度矩阵W构建度矩阵D,D是对角阵,Dii = Σj W_ij
- 计算拉普拉斯矩阵L = D - W
- 对L做特征值分解,取最小的k个特征值对应的特征向量(一般从第二个开始,排除全1向量)
- 把这k个特征向量按行拼成一个n×k的矩阵,每一行就是原节点在新特征空间的坐标
- 对这个n×k矩阵跑k-means聚类,聚出的k个簇就是最终的集群划分
Matlab核心代码长这样:
k = 4; % 集群数,后续用模块度确定 [V_eig, D_eig] = eig(L); % L是拉普拉斯矩阵 [eigvals, idx] = sort(diag(D_eig)); selected = V_eig(:, idx(2:k+1)); % 取k个非零最小特征值对应特征向量 clusterLabel = kmeans(selected, k, 'Replicates', 20);这里有个细节必须提醒:kmeans用的是欧氏距离,而特征向量每一列的尺度可能差很多,严格做法是先把特征向量按行归一化,再跑kmeans。我在代码里加了’Replicates’, 20,因为kmeans结果受初始中心影响,多重复几次取最优能降低随机性,后面还会细说这个坑。
3.3 集群数怎么定,划分结果怎么评价
谱聚类需要提前告诉它“要切几块”,也就是k值。k定得好不好,直接影响后面的电压控制效果。我的做法是画一条“k-模块度”曲线,选模块度最高且下降不明显的拐点。
模块度Q的定义是:
Q = (1/2m) Σ_ij [W_ij - (k_i·k_j)/(2m)] · δ(i,j同集群)
其中k_i是节点i的加权度,m是整个网络的总边权一半。Q值越大,说明集群内部连接越紧密、集群之间连接越稀疏,划分效果越好。实际运行中并不是k越大Q越高,通常会出现一个峰值,我那个33节点算例里,k=4时Q值大约是0.62,k=5掉到0.55,所以最终选了4个集群。
除了模块度,还会看几个辅助指标:一是集群规模是否均匀,别出现一个集群20个节点、另一个集群2个节点的情况,否则控制策略很难做;二是越限节点的分布,好的划分应该让电压越限节点相对集中在少数集群里,这样控制资源才能集中投放;三是集群内有效无功容量是否足够,如果某个集群内部几乎没有可调光伏,那这个集群注定是“空架子”,控制效果不会好。
4. 集群电压协调控制策略的设计与实现
4.1 控制框架:集群内自治优先、集群间协调兜底
集群划分完成之后,电压协调控制就有了一张“作战地图”。我用的控制框架是典型的三层结构,只不过在完全分布式和完全集中式之间取了平衡。
最底层是逆变器本地的无功电压下垂控制,光伏逆变器实时检测并网点电压,电压偏高就多吸收一点无功,电压偏低就多发出一点无功。这一层不依赖通信,响应最快,处理的是秒级以内的快速波动。
第二层是集群内部的自治控制,时间尺度在几十秒到几分钟。当集群内某个节点电压越限,先由该集群内部的无功资源去处理。处理依据就是前面算的S_VQ灵敏度矩阵,哪个逆变器对越限节点电压影响大,谁先动、多动。集群内部的通信压力小,因为只涉及本集群的几个节点。
第三层是集群间的协调控制。某个集群无功资源用尽仍然越限,就向相邻集群请求支援。这里“相邻”不是地理相邻,而是电气距离近、S_VQ灵敏度高的跨集群节点。这层控制在分钟级,相当于兜底方案。整套框架的好处是,大部分电压扰动在集群内部就消化了,全网协调只在极端场景下触发,通信和计算压力都小很多。
4.2 基于灵敏度优先序的逆变器无功调节算法
集群内部的控制逻辑,我实现了一个很实用且容易复现的“灵敏度优先序”算法,思路很直白:
- 先做一次潮流,找出电压越限节点集合
- 对每个越限节点,在它所属集群的可调光伏节点里,按S_VQ矩阵中该越限节点行对应元素从大到小排序
- 依次取灵敏度最高的光伏节点,计算它需要发或吸收的无功量
- 如果该光伏节点无功容量到顶,则取下一个节点,形成“接力调节”
- 更新注入功率,重新潮流,检查所有节点是否回到0.95~1.05 pu区间
调节量的计算方法很关键。假设节点i电压实测是Ui,目标是回到Ulimit,节点j是当前要调节的逆变器,灵敏度是S_VQ(i,j),则初步调节量为:
ΔQ_j = (U_i - U_limit) / S_VQ(i,j)
单位是Mvar。注意如果灵敏度是0.05,电压越限0.03 pu,那么理论上只需要0.6 Mvar无功就能拉回来。但实际因为无功调节本身会改变全网电压分布,一次算完可能需要两三轮迭代,所以我的代码里是循环执行的:调完一个节点,重算潮流,再看电压。
还有一个容易忽略的点:逆变器无功容量不是无穷的。工程上逆变器一般是按有功容量配的,无功能力受视在功率上限约束:
Q_max = sqrt(S_rated² - P_current²)
光伏满发时,P_current接近S_rated,Q_max就很接近0,也就是说大晴天中午越限最严重的时候,逆变器反而没什么无功余量可用了。所以算例里我特意把光伏有功设成0.8 pu而不是满发,并且在部分节点预留了无功容量,这样控制策略有发挥空间,也更符合实际中“光伏不能一直满发”的现实。
4.3 算例设计与控制效果对比
我在IEEE 33节点系统上做了改造:在节点9、17、21、24、30分别接入分布式光伏,总装机约1.8 MW,场景设置为午间轻载,光伏出力0.8 pu,负荷只有额定的一半,这是最容易出现电压越限的组合。
潮流算出来,控制前节点17的电压最高,达到1.073 pu,明显越上限;节点9、21也越限,分别是1.055和1.052 pu。越限节点基本聚集在集群3和集群4,说明集群划分很精准地把“问题聚集区”圈出来了。
控制策略启动后,按灵敏度优先序在集群内部调节。集群3中有两台光伏逆变器灵敏度最高,它们各吸收约0.25 Mvar无功,电压就从1.073降到了1.045;集群4里另一台逆变器吸收了0.18 Mvar,把节点21拉到1.038。整体调节量加起来不到0.7 Mvar,就把全网最严重的越限都消除了,而且所有节点电压都回到1.05 pu以下,最低节点也没有低于0.97 pu。
控制前后的电压分布对比如下:
| 节点 | 控制前电压(pu) | 控制后电压(pu) | 是否越限 |
|---|---|---|---|
| 9 | 1.055 | 1.031 | 已恢复 |
| 17 | 1.073 | 1.045 | 已恢复 |
| 21 | 1.052 | 1.038 | 已恢复 |
| 24 | 1.041 | 1.022 | 正常 |
| 30 | 1.033 | 1.019 | 正常 |
这个结果说明两件事:一是集群划分把电气上强相关的节点聚在一起,集群内部灵敏度高,协调起来效率很高;二是基于灵敏度的调节策略比“所有逆变器平均出力”更省资源,调节总量小了将近一半。
5. Matlab代码实现要点与踩坑记录
5.1 工程化代码的模块划分
我强烈建议不要把所有逻辑堆在一个脚本里,否则后面改一个参数可能要翻几百行代码。我的工程结构是这样的:
case33.m:基础数据文件,定义线路参数、负荷、光伏接入位置和容量BuildYbus.m:根据线路参数生成节点导纳矩阵NRLF.m:牛拉法潮流计算函数CalSensitivity.m:基于雅可比矩阵计算S_VQ灵敏度矩阵SpectralCluster.m:谱聚类划分主体VoltageCtrl.m:集群电压协调控制主函数run_main.m:流程总控,依次调用上面这些模块,并输出结果
这样做的好处是每个文件都能单独测试。我调代码时习惯先把BuildYbus和NRLF单独跑通,确认潮流结果和标准数据一致后,再做集群划分,最后才接控制逻辑。分层验证能省掉大量联调时间。
5.2 这几个坑,我替你踩过了
先说一下最容易让新手崩溃的:光伏节点类型选择。很多人习惯把并网光伏当PV节点处理,但PV节点在牛拉法里需要指定无功上下限,实际逆变器无功容量又和当前有功出力密切相关,处理不好非常容易算飞。我的做法是把光伏节点全部简化为PQ节点,初始无功设为0,但在电压控制阶段把逆变器无功容量作为约束加进去,调节时动态更新。这样潮流计算稳定,控制逻辑也清晰。
第二个坑是kmeans随机性导致集群划分结果每次都不一样。谱聚类最后一步用kmeans,初始中心是随机的,不固定随机种子的话,同一套数据两次运行出来的集群边界可能不同。我用了两个手段:一是kmeans加’Replicates’, 20,让Matlab多次运行取最优;二是在主脚本最前面加rng(2024)固定随机种子。这样复现论文结果时不会出幺蛾子。
第三个坑是牛拉法初值问题。配电网线路的X/R比远低于输电网,电阻分量不能忽略,初值给得不好收敛很慢甚至振荡。我实践下来,电压初值给1.0 pu、相角给0基本没问题,但如果节点数多或者接入重负荷,建议先把负荷按比例逐步加入,做一个“连续潮流”式的渐进迭代,稳定性好很多。
第四个坑和Matlab计算习惯有关:不要用inv()显式求逆矩阵。我刚学Matlab时习惯写Zbus = inv(Ybus),节点数少没事,节点多了速度骤降,而且精度损失累积后灵敏度矩阵都不可信了。正确做法是Zbus = Ybus \ eye(n),解灵敏度方程时也用反斜杠运算符。
5.3 结果可视化和写报告出图技巧
做完了控制仿真,最后一步是出图。这部分做得好不好,直接决定你论文或者报告的说服力。我通常画三张图:一是控制前后的全网电压分布曲线,横轴节点编号,纵轴电压标幺值,加上0.95和1.05的上下限水平线,一眼就能看出越限和控制效果;二是集群划分结果的可视化,把配电网拓扑画成图,用不同颜色标注不同集群,这是论文里非常经典的一张图;三是各光伏节点无功调节量的柱状图,展示集群内协调的调节量分配。
画集群拓扑时,我用的方法是graph对象加plot函数,节点坐标自己定义,线宽按线路阻抗设个比例,颜色按集群标签分配。核心代码大概是这样:
G = graph(s, t, weights); p = plot(G, 'XData', xcoord, 'YData', ycoord, 'LineWidth', 1.5); p.NodeCData = clusterLabel; colorbar;这里有个小经验:出图前设置好字体和线宽,直接用默认样式往往偏小、偏细,放论文里不清晰。建议figure创建后先set(gcf,'Color','w'),坐标轴set(gca,'FontSize',11),线宽统一加到1.5以上。保存图片时用exportgraphics或print -dpng -r300,不要截图贴在Word里,清晰度完全不在一个量级。
最后分享一条实际调代码时对我帮助很大的经验:集群划分和电压控制是两个问题,调试时一定要分开测。先只跑集群划分,确认划分结果稳定且模块度合理,再单独调试电压控制——单独调试时甚至可以先用固定集群标签,跳过聚类环节,只测调节逻辑。等两边都各自测稳了,再拼接起来。否则两个问题叠加在一起,出了bug你根本定位不到底是划分不对还是控制算法写错了。我先在这条上浪费了两天,希望你不用再走这个弯路。