☰
EKF和UKF电力系统动态状态估计的Matlab实现与对比解析
2026/10/9 6:50:24 网站建设 项目流程

最近把基于EKF(扩展卡尔曼滤波)和UKF(无迹卡尔曼滤波)的电力系统动态状态估计在Matlab上从建模到滤波循环完整实现了一遍,测试系统用的IEEE 14节点,跑了多组噪声和初始偏差的仿真,精度、耗时、发散情况都做了对比。这两类算法在电力系统动态状态估计里属于绕不开的经典路线,研究生课题、毕业设计、工程预研基本都会碰到。这篇文章就把整个实现过程拆开来讲:先说明为什么动态状态估计非做不可,再分别拆EKF和UKF的原理和代码,最后把我在调参和debug时踩过的坑全部列出来,你可以直接照着跑通自己的仿真。


1. 为什么电力系统偏偏需要"动态"状态估计

1.1 静态状态估计的天然盲区

传统上大家提到电力系统状态估计,第一反应都是静态加权最小二乘(WLS)。它在SCADA系统里用得最多,目标是基于某一时刻的冗余量测,解算出该断面的节点电压幅值和相角。这个思路本身没有错,但它有两个隐含假设:一是系统处于稳态,二是量测数据比较稀疏且有延迟。在这两个假设下,WLS给出的状态对监控调度是够用的。

但现在的电网情况变了。新能源占比上来之后,出力波动性明显增大,扰动不再只是偶发的短路或切机,而是频繁的小幅功率振荡、风电场出力波动、光伏云层遮挡导致的出力变化。这些动态过程意味着发电机功角、角速度、暂态电动势等变量时刻都在变化,而且是强非线性变化。如果还拿静态估计去"拍一个断面照片",拍出来的时点滞后不说,许多动态中间过程根本捕捉不到。

1.2 动态状态估计要解决的到底是什么

动态状态估计本质上是在做一件事:把卡尔曼滤波那一套"预测-校正"闭环用到电力系统状态变量上。上一时刻我们有一个状态估计值,通过发电机转子运动方程等动态模型,可以预测当前时刻状态会变成什么样;然后利用当前时刻的PMU量测做校正,把预测误差修回来。

这里有个关键点需要明确:动态状态估计估计的不是节点电压本身,而是系统的真实动态状态,最常见的就是发电机转子功角δ和角速度ω。功角是系统暂态稳定分析中最核心的物理量,它能直接反映发电机之间的相对摆动趋势。电压幅值相角这些量更多体现在量测方程里,是观测状态的"窗口"。

PMU(同步相量测量单元)的出现给动态状态估计提供了数据基础。PMU能以50Hz甚至100Hz的采样率输出带时标的电压、电流相量,这意味着状态估计器可以从"秒级更新"进入"毫秒级更新"。没有这个时间分辨率,动态状态估计根本跑不起来。

1.3 为什么EKF和UKF是主流入门选择

电力系统的动态模型是非线性的,这就把线性卡尔曼滤波挡在了门外。处理非线性状态估计的算法很多,但EKF和UKF占据了绝大多数研究场景,原因很实际:

  • EKF通过对非线性函数做一阶泰勒展开,把问题线性化之后继续沿用卡尔曼滤波框架,思路直观,实现代码量小,计算速度快。缺点是需要显式求雅可比矩阵,而且精度只有一阶截断。
  • UKF用一组确定性采样点(Sigma点)直接经过非线性函数传播,再统计传播后样本的均值和协方差,从原理上避免了求导,而且精度至少达到二阶,对强非线性场景更稳。

在Matlab里,这两种算法的代码量其实都不大,核心循环几十行就能写完。但"代码能跑"和"仿真结果可信"之间还隔着模型离散化、参数匹配、噪声设置等一系列坑,下面逐一展开。


2. 从发电机模型到可计算的离散状态空间

2.1 发电机动态方程是滤波器的"心脏"

做动态状态估计第一步不是写滤波算法,而是先确定状态方程和量测方程。状态方程描述状态量如何随时间演化,量测方程描述量测与状态之间的映射关系。

在经典二阶模型下,第i台发电机的状态变量取为功角δi和电角速度ωi,连续时间动态方程为:

dδi/dt = ωi - ω0 Mi * dωi/dt = Pmi - Pei - Di*(ωi - ω0)

其中ω0是同步转速角频率(工频50Hz对应的314.159 rad/s),Mi=2Hi是发电机惯性时间常数(单位秒),Di是阻尼系数,Pmi是机械功率,Pei是电磁功率。

需要注意,Pei并不是状态的简单代数函数,它取决于发电机内电势、机端电压以及整个网络的潮流分布。在单机无穷大模型下,Pei可以写成E'q*V/x'sin(δ)这种简单形式;但到了IEEE 14节点这种多机系统,Pei必须通过网络方程求解,通常要结合潮流计算或者事先化简出的网络导纳矩阵来算。

2.2 量测方程:状态如何被观测到

量测方程h(x)描述的是:给定一组功角、角速度状态,PMU在母线上应该看到什么。以IEEE 14节点系统为例,PMU一般布置在发电机出口母线附近,量测通常包括:

  • 母线电压幅值Vi和相角θi
  • 发电机注入母线的有功功率Pi和无功功率Qi
  • 部分关键线路的潮流值

这些量测和状态之间的关系是非线性的,本质上是潮流方程的反向映射:由发电机内电势和功角出发,通过网络方程求出各母线电压,再进一步得到线路功率。所以h(x)的实现通常会复用潮流计算的函数逻辑,把它封装成一个"给定状态,输出量测"的黑盒函数。

量测噪声按典型PMU精度来设就够:电压幅值标准差0.001~0.002 pu,相角标准差0.01~0.02 rad,功率标准差0.01 pu左右。这个范围是根据PMU实际测量精度和多次仿真经验得出来的,设得过大过小都会直接影响滤波器收敛性。

2.3 离散化:很多人跳过但最影响结果的一步

动态状态估计用的是离散卡尔曼滤波框架,但电力系统动态模型是连续时间微分方程,所以必须做离散化。这一步在不少论文里被一句话带过,实际实现时它直接影响滤波精度。

最省事的做法是前向欧拉法:

δ(k+1) = δ(k) + Δt*(ω(k) - ω0) ω(k+1) = ω(k) + Δt*(Pm - Pe(k) - D*(ω(k) - ω0))/M

当采样间隔Δt取0.01s(对应100Hz PMU)时,欧拉法的离散误差通常可以接受。如果追求更高精度,可以用四阶Runge-Kutta,代码也不复杂。我建议至少在状态预测这一步用RK4,因为EKF/UKF的性能上限很大程度取决于状态预测准不准。量测方程h(x)不涉及时间离散,直接是代数计算。

状态方程离散化之后,还要给过程噪声建模。过程噪声w(k)代表模型误差和外部扰动,协方差矩阵Q通常在仿真里设为对角阵。它的物理意义是"你有多相信状态方程"。Q设太大,滤波器会过度信任量测,估计值噪声大;Q设太小,滤波器跟不上真实动态,出现滞后误差。


3. EKF和UKF的核心逻辑拆解

3.1 EKF:一阶线性化的成与败

EKF的思路一句话就能说清:既然系统非线性,那就把它在当前状态附近线性化。具体做法是求f和h的雅可比矩阵F和H,然后完全套用线性卡尔曼滤波的公式。

EKF的滤波循环包含五步:

1. 状态预测:x_pred = f(x_est_prev) 2. 协方差预测:P_pred = F*P_est_prev*F' + Q 3. 卡尔曼增益:K = P_pred*H'*(H*P_pred*H' + R)^(-1) 4. 状态更新:x_est = x_pred + K*(z - h(x_pred)) 5. 协方差更新:P_est = (I - K*H)*P_pred

这里面F是状态方程对x的雅可比矩阵,H是量测方程对x的雅可比矩阵。在电力系统场景下,H矩阵某种意义上和你熟悉的潮流雅可比矩阵是一家人——都是功率/电压对相角的偏导数。如果你有潮流计算的灵敏度矩阵,甚至可以改造复用来校验H的准确性。

值得注意的是,EKF的线性化有两个隐患。第一,雅可比矩阵是在预测点x_pred处计算的,如果预测值和真值偏差太大,线性化误差会被放大;第二,强非线性函数的一阶泰勒展开本身就丢掉了高阶信息,在系统状态剧烈变化时精度会明显下降。

实现EKF时,我强烈建议用数值微分代替解析求导来算雅可比矩阵。电力系统状态方程和量测方程的解析导数推导繁琐且容易错,用中心差分法在代码层面做灵敏度估计,足够可靠:

F_ij = (f_i(x + h_ij) - f_i(x - h_ij)) / (2*h_j)

扰动步长h_j的经验取法是:h_j = sqrt(eps)*max(abs(x_j), 1),其中eps是Matlab的浮点精度。这个取值兼顾了数值截断误差和计算机舍入误差,不容易踩到坑。

3.2 UKF:用Sigma点绕开所有求导

UKF的核心是"无迹变换"(Unscented Transform)。它不再对非线性函数做泰勒展开,而是构造一组带权重的Sigma点,让这些点经过非线性函数传播后,用统计方法恢复出传播后分布的均值和协方差。

Sigma点生成方式有很多种,最常用的是对称采样。假设状态维度是n,通过参数α、β、κ构造缩放参数λ:

λ = α^2*(n + κ) - n

然后对协方差矩阵P做Cholesky分解,生成2n+1个Sigma点:

X(0) = x_mean X(i) = x_mean + sqrt((n+λ)*P)_i 的列,i=1..n X(i+n) = x_mean - sqrt((n+λ)*P)_i 的列,i=1..n

对应权重为:

Wm(0) = λ/(n+λ) Wc(0) = λ/(n+λ) + (1 - α^2 + β) Wm(i) = Wc(i) = 1/(2*(n+λ))

这里的α控制Sigma点围绕均值的散布程度,通常取1e-3到1之间;κ一般取0或者3-n;β根据先验分布特性取值,高斯分布下β=2是最优的。

UKF滤波循环就是"Sigma点生成->状态传播->统计还原->量测传播->互协方差->增益更新"这条链路。代码里最重要的一步是:所有Sigma点都要分别经过状态方程f和量测方程h,得到一组传播后的样本点,然后加权求和还原均值和协方差,再和EKF一样计算卡尔曼增益。

3.3 EKF和UKF的差异该怎么选

用一张表把关键差异收敛起来,方便你做选择:

对比维度EKFUKF
线性化方式一阶泰勒展开Sigma点无迹变换
精度一阶截断至少二阶
雅可比矩阵需要(解析或数值)不需要
计算量小,滤波循环两次函数求值较大,2n+1个点到f和h各传播一次
强非线性表现可能出现线性化失效更稳定
实现复杂度简单中等
适用场景模型较平滑、实时性要求高扰动剧烈、追求稳定性、离线分析

在我实测的IEEE 14节点场景中,两者精度差距并不夸张,但如果故意把初始状态偏差调大,或者把量测噪声调高,EKF出现发散的概率明显高于UKF。这也符合理论预期:初始偏差大时,预测点的线性化误差更大,一阶近似容易失效。


4. Matlab实现:从数据构造到完整滤波循环

4.1 准备IEEE 14节点算例与真值轨迹

我采用的是IEEE 14节点系统,包含5台发电机。在Matlab里建议配合Matpower使用,它可以方便地计算出稳态潮流结果。需要强调一个容易出错的细节:Matpower中bus和gen数据结构里的列索引要和发电机编号正确对应,否则组装状态向量时发电机顺序错位,后面全盘皆错。

真值轨迹的生成可以采用"稳态初值+扰动激励"的方式。先计算稳态潮流得到初始功角、角速度(角速度稳态为ω0,功角来自潮流结果),然后在机械功率或负荷上施加一个短时扰动(比如在1s时刻给某台发电机的机械功率阶跃上升4%),用数值积分生成一段时间内的真实状态轨迹。这样得到的轨迹就是滤波器要估计的"真值"。

量测数据由真值轨迹套用量测方程h(x)生成,再叠加高斯噪声。这样的好处是给真值轨迹加噪声时,我们确切知道噪声统计特性,后面评估EKF和UKF精度才有依据。

仿真参数我建议这样设:

  • 采样间隔Δt = 0.01s(100Hz PMU量测频率)
  • 仿真时长10s,共1000个采样点
  • 状态维度n = 10(5台发电机,每台2个状态)
  • 过程噪声协方差Q = 1e-6 * eye(n),后续根据结果微调
  • 量测噪声协方差R按前面提到的PMU典型精度设

4.2 EKF核心循环:Matlab代码

EKF的代码重点在两个地方:数值雅可比矩阵和滤波主循环。下面是数值雅可比计算的辅助函数:

function J = numericalJacobian(func, x, params) n = length(x); J = zeros(n, n); for i = 1:n h = sqrt(eps) * max(abs(x(i)), 1); x_plus = x; x_plus(i) = x(i) + h; x_minus = x; x_minus(i) = x(i) - h; J(:, i) = (func(x_plus, params) - func(x_minus, params)) / (2*h); end end

注意H矩阵的数值雅可比维度是(量测维度 × 状态维度),和F矩阵维度不同,要分别计算。EKF主循环代码如下:

x_est = x0; P_est = P0; for k = 1:N % 状态预测 x_pred = stateFunc(x_est, params); F = numericalJacobian(@stateFunc, x_est, params); P_pred = F * P_est * F' + Q; % 量测预测 z_pred = hxFunc(x_pred, params); H = numericalJacobian(@hxFunc, x_pred, params); % 卡尔曼更新 S = H * P_pred * H' + R; K = P_pred * H' / S; x_est = x_pred + K * (z_meas(:, k) - z_pred); P_est = (eye(n) - K * H) * P_pred; % 保存估计序列 x_est_seq(:, k) = x_est; end

这里stateFunc就是离散化的状态方程,hxFunc是量测方程。两步都封装成"输入状态向量+系统参数,输出结果向量"的形式,这样数值雅可比才能统一复用。

4.3 UKF核心循环:Matlab代码

UKF没有求导步骤,核心在Sigma点生成和权重计算。先把这两个独立函数写出来:

function [Xi, Wm, Wc] = sigmaPoints(x, P, alpha, beta, kappa) n = length(x); lambda = alpha^2 * (n + kappa) - n; Wm = zeros(2*n+1, 1); Wc = zeros(2*n+1, 1); Wm(1) = lambda / (n + lambda); Wc(1) = lambda / (n + lambda) + (1 - alpha^2 + beta); Wm(2:end) = 1 / (2*(n + lambda)); Wc(2:end) = Wm(2:end); S = chol((n + lambda) * P, 'lower'); Xi = zeros(n, 2*n+1); Xi(:, 1) = x; for i = 1:n Xi(:, i+1) = x + S(:, i); Xi(:, i+n+1) = x - S(:, i); end end

采样间隔、Q、R的设置和EKF保持一致,方便公平对比。UKF主循环:

x_est = x0; P_est = P0; for k = 1:N % 生成Sigma点 [Xi, Wm, Wc] = sigmaPoints(x_est, P_est, alpha, beta, kappa); % 状态传播 Xi_pred = zeros(n, 2*n+1); for i = 1:2*n+1 Xi_pred(:, i) = stateFunc(Xi(:, i), params); end x_pred = Xi_pred * Wm; P_pred = Q; for i = 1:2*n+1 d = Xi_pred(:, i) - x_pred; P_pred = P_pred + Wc(i) * (d * d'); end % 量测传播 Zi_pred = zeros(m, 2*n+1); for i = 1:2*n+1 Zi_pred(:, i) = hxFunc(Xi_pred(:, i), params); end z_pred = Zi_pred * Wm; % 协方差与增益 Pzz = R; Pxz = zeros(n, m); for i = 1:2*n+1 dz = Zi_pred(:, i) - z_pred; dx = Xi_pred(:, i) - x_pred; Pzz = Pzz + Wc(i) * (dz * dz'); Pxz = Pxz + Wc(i) * (dx * dz'); end K = Pxz / Pzz; % 更新 x_est = x_pred + K * (z_meas(:, k) - z_pred); P_est = P_pred - K * Pzz * K'; x_est_seq(:, k) = x_est; end

UKF代码量比EKF大,但逻辑线性化程度高,里面所有"传播Sigma点"的循环都是并行的。在Matlab里如果状态维度比较大,建议把这些循环向量化或改用parfor,否则计算时间会明显拖后腿。

4.4 性能评价指标怎么算

只画轨迹图不够,量化指标才是写论文和评估算法的核心。我建议至少统计三类指标:

  • 均方根误差(RMSE):对每个状态变量分别统计RMSE,画出随时间变化曲线,直观反映滤波器的动态跟踪能力。
  • 稳态平均绝对误差(MAE):在扰动平息后的后半段统计,反映滤波器的稳态精度。
  • 单步平均耗时:用tic/toc包围滤波主循环,除以总步数,比较EKF和UKF的计算效率。这个数据在工程预研时很有说服力。

5. 实测对比:精度、速度与鲁棒性

5.1 不同噪声水平下的估计精度

我按前面参数跑完两组仿真后,典型结果如下(具体数值会因扰动场景和噪声随机种子不同而略有浮动,看趋势就好):

场景功角RMSE(度)角速度RMSE(rad/s)滤波器
小噪声(PMU标准精度)0.420.0084EKF
小噪声0.360.0072UKF
大噪声(噪声放大3倍)1.050.0221EKF
大噪声0.610.0138UKF
初始偏差较大(功角偏5度)发散/大幅震荡—EKF
初始偏差较大0.890.0172UKF

在小噪声且初始值准确时,EKF和UKF的性能差距不大,这一点和很多文献结论一致——不要指望UKF在所有场景都碾压EKF。但把初始偏差拉大或者强扰动出现时,UKF的稳定性优势就体现得很明显,EKF容易在预测点附近产生过大的线性化误差,导致协方差矩阵失去正定性。

5.2 计算代价的取舍

速度方面,EKF的优势是实实在在的。由于每次循环只需算两次函数求值(一次预测、一次量测)外加两组数值雅可比,在状态维度等于10时,EKF单步耗时大约在0.5~1ms量级;UKF要传播21个Sigma点,每个点都要过一次状态方程和量测方程,单步耗时大约在EKF的2到4倍。

这个差距在离线仿真中完全不是问题,但如果后面要接实时闭环或硬件在环,EKF仍然是更务实的选择。研究场景里如果更关注稳定性,UKF多出来的那点算力成本是值得的。

5.3 一个容易被忽略的对比维度:对协方差初值的敏感度

不少人在仿真里把P0设成单位阵或者很小的对角阵,觉得滤波器总会自己收敛。实际跑下来,EKF对P0的敏感度比UKF高不少。原因是EKF线性化依赖预测点质量,而预测点质量又受到协方差传播的影响。P0设得过小,滤波器过于相信初值,头几步修正能力被严重削弱;P0设得过大,增益会先大后小,容易在初始段产生明显超调。UKF因为Sigma点在整个分布范围内传播,对P0的病态程度相对更耐受。

所以如果你只有一次调参机会,我建议把P0设成"比你对初值的置信度略保守一点"的对角阵,比如功角对应的方差取(2度)^2,角速度取(0.05 rad/s)^2,而不是随便给个归一化数值。


6. 实操中的坑与调试经验

6.1 Q和R不是随便拍脑袋写的

Q和R的比例直接决定了滤波器偏向"信任模型"还是"信任量测"。我在调试中最典型的失败经历是:为了追求响应速度,把Q设成1e-2量级,结果量测噪声直接透过滤波器,估计轨迹毛刺严重;反过来把Q设成1e-9,滤波器对突变扰动完全没有反应,跟着模型走出一条滞后轨迹。

比较靠谱的调参路径是:先用稳态数据估计R——把PMU量测方差算出来,按实测噪声水平设R;然后从较小的Q开始,逐渐加大,观察新息序列(innovation,即z - h(x_pred))的变化。如果新息均值持续偏离零,说明Q太小或者模型有偏;如果新息方差远大于理论值S,则说明Q相对R偏小,滤波器没有跟上真实动态。

6.2 数值雅可比扰动步长和角度单位

用数值雅可比虽然省了推导,但步长选不对照样翻车。步长太大,函数非线性导致偏导数失真;步长太小,舍入误差主导。我用的经验公式是前面提到的sqrt(eps)*max(abs(x),1),并用中心差分。另一个大坑是角度单位不统一:摇摆方程里角速度用rad/s,功角用rad,但很多潮流计算习惯用度。如果stateFunc里混入度数,雅可比计算和协方差传播的数值量级都会出现病态,滤波器会莫名其妙发散。建议全程用rad,只在最后画图时转成度。

6.3 滤波器突发散怎么排查

发散是动态状态估计仿真里最常见的焦虑来源。我的排查顺序是固定的:

  1. 先检查模型本身:单独调stateFunc,用初始状态积分几步,看输出是否符合物理规律(功角因扰动增大、随后回转等)。
  2. 检查量测方程实现:给出一组已知状态,手动算出量测,对比hxFunc输出,确认没有符号或矩阵索引错位。
  3. 检查协方差对称性:每经过一次P_pred或P_est计算,马上检查P - P'的范数是否几乎为零。数值噪声会让P失去对称正定性,必要的时候加一行P = (P + P')/2做对称化,再配合P = nearestSPD(P)之类处理。
  4. 看新息序列:把新息画出来,正常应该围绕零小幅波动;如果持续偏移或爆发式跳变,说明预测和量测之间存在系统性偏差。

还有一个藏得很深的坑:量测向量维度和量测方程内部计算不一致。比如你声明量测有12维,但hxFunc里数组拼错少了一行,Matlab矩阵运算大概率不会报错,而是在某个循环里悄悄广播错误值,滤波结果可能整体偏移。这种问题靠肉眼很难看出来,我的办法是在仿真前加一个assert维度检查。

6.4 量测和状态的量级差异怎么处理

IEEE 14节点里,电压幅值大约在1.0 pu量级,功角在十几度也就是0.2~0.3 rad量级,角速度只在ω0附近波动,偏差通常是0.01 rad/s量级。这三个量级放在同一个状态向量和协方差矩阵里,很容易出现数值问题。

处理办法有两个方向:一是对量测做归一化,把单位量纲差异通过R矩阵的缩放吸收掉;二是更推荐的方案,保持物理单位,但在设Q和R时严格按物理量的量级来构造对角元素。例如功角的过程噪声方差可以设在(1e-4)^2,角速度在(1e-3)^2,而电压幅值量测噪声在(1e-3)^2,相角在(2e-2)^2。这样既不损失物理意义,矩阵条件数也在可控范围。

我个人在实际操作中的一个体会是:动态状态估计90%的精力都在模型和噪声统计上,算法本身反而是最不需要反复折腾的部分。EKF和UKF的代码量在一百五十行以内,但状态方程、量测方程、初值和噪声协方差这一套组合拳,需要对电力系统动态过程有足够的理解才能配合到位。如果你刚开始做这类仿真,建议先在一个单机无穷大系统上把EKF跑通,再扩展到IEEE 14节点;直接在多机系统上起步,遇到发散很难分清是模型问题还是算法问题。最后一个小技巧:所有滤波结果先不要看估计轨迹,先画量测残差时间序列,残差正态就说明滤波器状态健康,残差有明显的结构模式,那滤波器的模型一定还有没榨干的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询