☰
电力系统动态状态估计:Matlab中EKF与UKF算法对比实践
2026/10/13 12:16:04 网站建设 项目流程

做电力系统动态状态估计,Matlab里绕不开的两条路就是扩展卡尔曼滤波(EKF)和无迹卡尔曼滤波(UKF)。之前我在一个发电机状态跟踪项目里把这两套算法都完整落了一遍,跑通之后最大的感受是:EKF看似简单,但雅可比矩阵算错一个符号结果就全歪;UKF虽然不用求导,但sigma点的生成和权重设置又有一堆隐性坑。所以我把整个实现过程、参数设置、仿真算例和踩坑记录整理出来,给正在做EKF、UKF与Matlab电力系统动态状态估计的朋友一份能直接参照的完整方案。这篇文章会从算法原理讲起,给出可运行的Matlab代码结构、仿真场景设计和常见问题排查,适合做动态安全分析、PMU数据处理、课程设计或毕业设计的同学。

1. 项目概述与问题定义

1.1 为什么电力系统需要“动态”状态估计

传统的潮流计算解决的是“这个稳态运行点在哪”的问题,给一组负荷和发电机出力,算出各节点电压和相角。但电力系统运行中面临的是持续的扰动——负荷随机波动、新能源出力间歇、故障切除后的功角摇摆,系统根本不在严格的稳态点上。调度人员关心的是发电机功角、转子转速这些随时间变化的动态量,它们直接反映系统是否稳定、发电机是否失步。

可问题是,功角和转速这类状态量大部分不能直接测量,或者测量成本极高。能量测到的是节点电压幅值、注入功率、线路潮流这类间接量,而且PMU、SCADA的量测还有噪声、延时、坏数据。动态状态估计干的事情,就是利用非线性滤波算法,把不可直测的状态从带噪声的量测中在线递推出来。这也是为什么状态估计在电力系统中不只是学术概念,而是能量管理系统里的一个实际功能模块。

1.2 为什么要拿EKF和UKF做对比

电力系统的动态模型,比如发电机转子运动方程、励磁系统模型,以及量测方程里电压、功率和功角之间的三角函数关系,都是典型的非线性关系。线性卡尔曼滤波在这里直接用不了,必须用非线性版本。

EKF的思路是把非线性函数在当前状态附近做一阶泰勒展开,用雅可比矩阵替代线性卡尔曼里的状态转移矩阵和量测矩阵,这是最经典、历史最久的做法。UKF则换了一条路:不展开非线性函数,而是用一组确定性的采样点(sigma点)去逼近状态分布,再让这些点直接穿过非线性函数,用统计量重构均值和协方差。

两者谁更优,不能凭感觉,必须在同一套仿真模型下用相同噪声、相同初值去对比。这个项目的核心就是建立这样一套Matlab仿真环境,跑出数值结果,用RMSE、平均绝对误差这些指标说话。

1.3 这套Matlab方案适合谁参考

如果你是正在处理PMU量测数据的工程师,这篇内容可以给你提供滤波器的完整调用框架,直接替换自己的状态方程和量测方程就能跑。如果你是本科生或研究生在做卡尔曼滤波相关的课程设计、毕业设计,我用到的建模方法和代码结构是简化但完整的最小实现,你可以在它基础上扩展成多机系统、励磁模型甚至平方根UKF。

如果你之前完全没接触过卡尔曼滤波,也不用慌。我建议你先把第二章的原理看懂,再对照第三章的代码一步步敲,先跑通单机系统,再去动参数看效果,最后再回头看第四章的结果分析和第五章的踩坑记录,这个过程走完基本就打通了。

2. 算法原理拆解:EKF和UKF的差别与取舍

2.1 状态估计的统一框架:预测+更新

EKF和UKF虽然实现方式不同,但骨子里遵循的都是同一个递归框架,包括两个阶段。

第一阶段是时间更新。利用已知的动态模型,把当前时刻的状态和协方差外推到下一时刻。在这个阶段,状态转移函数f(x, u, w)决定了过程噪声如何被传播到状态预测中,这一步骤相当于“依据系统模型进行预测”。第二阶段是量测更新。当新的量测数据到达时,把量测预测值与实际量测值做差,乘以卡尔曼增益,得到对状态的修正。增益的大小由预测协方差和量测噪声R的相对关系决定:量测越可靠,增益越大,滤波结果越愿意相信量测;反之则更依赖模型预测。

理解这个框架很重要,因为EKF和UKF的所有区别都只体现在“协方差如何传播”和“增益如何计算”这两个环节上,其余外壳完全一致。这就像换发动机不换车身,很多代码结构是可以共用的。

2.2 EKF的核心:雅可比矩阵线性化

EKF的思路非常直观:既然非线性函数没法直接套用卡尔曼公式,那就把函数在当前估计点处做泰勒展开,保留一阶项。状态转移矩阵F由状态方程对状态向量求偏导得到,量测矩阵H由量测方程对状态向量求偏导得到,然后在预测和更新阶段分别用F和H替代线性卡尔曼中的转移矩阵与量测矩阵。

听起来很简单,但电力系统的状态方程和量测方程里充满了sin和cos函数,求导的中间过程很容易出错。比如对功角δ求有功功率P的偏导时,需要用到P对sin(δ-θ)的链式法则,任何一个符号错了,滤波结果可能直接就发散。

在Matlab实现里,有两种求雅可比的方式:解析法和数值差分法。解析法计算效率高、精度好,但推导麻烦,每次改模型都要重新推。数值差分法实现极其简单,比如用central difference公式,对每个状态分量加一个小扰动δx,重新计算函数值,再用差分近似偏导。我实际开发中总是先用数值差分法验证整体流程,跑通之后再考虑换成解析雅可比。这样做能快速定位“是滤波算法错了还是雅可比矩阵错了”这类问题。

2.3 UKF的核心:sigma点变换

UKF的核心思想是无迹变换。它基于一个认知:对高斯分布做非线性变换时,与其去逼近非线性函数,不如去逼近状态的分布。具体做法是取一组精心选择的采样点(sigma point),让它们的样本均值和样本协方差等于当前状态的均值和协方差,然后把每个sigma点分别代入非线性函数,得到变换后的一组点,再从这组点里重构输出均值和协方差。

sigma点的选取规则是:给定n维状态向量,生成2n+1个点。第一个点是状态均值本身,剩下的点沿着协方差矩阵的特征方向,以固定步长向两侧偏移。偏移步长由参数α、κ和缩放因子λ决定。参数α控制sigma点离均值有多远,通常取1e-3到1之间;κ一般取0;β用来合并先验分布的高阶信息,对高斯分布取2最优。

UKF不需要计算雅可比矩阵,而且对非线性函数的近似精度达到了三阶,比EKF的一阶线性化更高。代价是每个滤波周期要调用2n+1次状态函数和量测函数,计算量大约翻倍,但在绝大多数电力系统应用场景里这个计算量完全能接受。

2.4 在电力系统场景下两套算法怎么选

从我实际对比的情况看,在状态方程非线性程度一般、量测更新频率较高的场景下,EKF和UKF的精度差距并不大,EKF在计算速度上略占优势。但当系统扰动较强、状态轨迹偏离线性化工作点较远时,EKF的一阶近似误差会累积,滤波误差明显增大,而UKF因为保留了高阶信息,表现更稳。

另一个维度是工程实现成本。EKF的雅可比矩阵推导是硬骨头,尤其是状态方程里包含饱和函数、死区非线性时,解析雅可比几乎写不出来。这时UKF反而成为更简单的选择,只要把非线性函数本身写对,剩下交给sigma点。所以我个人在电力系统动态状态估计里的选型建议是:简单系统、追求速度,用EKF;非线性强、模型复杂,用UKF;如果做研究和对比分析,两套都实现,互相验证。

3. 建模与Matlab实现:从状态方程到可运行代码

3.1 状态变量与动态模型怎么定

我用的是经典的单机无穷大系统模型,这个模型能反映电力系统动态状态估计的核心问题,又不会让代码复杂到无法阅读。

状态向量取发电机的功角δ和转速ω,即x = [δ; ω]。离散化的转子运动方程可以写成:

function x_next = f_sys(x, dt, param) delta = x(1); omega = x(2); Pm = param.Pm; D = param.D; M = param.M; H = param.H; % 简化同步功率系数 omega0 = param.omega0; dDelta = omega0 * (omega - 1); dOmega = (Pm - H * sin(delta) - D * (omega - 1)) / M; x_next = x + dt * [dDelta; dOmega]; end

这里Pm是机械功率,H sin(delta)表示电磁功率的简化模型,D是阻尼系数。如果你要扩展到多机系统,只需要把状态向量扩展成所有发电机的δ和ω堆叠,函数内部改成对应维度的矩阵运算即可。

量测方程我取发电机的有功输出作为量测,因为有功功率和功角之间是正弦关系,能充分体现非线性:

function y = h_meas(x, param) delta = x(1); H = param.H; y = H * sin(delta); end

如果使用PMU量测,一般还可以加入机端电压幅值、相角甚至线路潮流作为量测向量。这时量测方程的输出y会从标量变成列向量,代码里对应的协方差矩阵维度也要相应扩展。

3.2 量测噪声与过程噪声的设置

滤波器的性能很大程度由协方差矩阵Q和R决定。Q对应过程噪声,代表模型的不确定性,包括建模误差、参数误差、负荷扰动等;R对应量测噪声,主要来自传感器误差。

我常用的经验是:首先根据量测设备的精度确定R。比如PMU相量测量的典型误差大约在0.01到0.05度,对应的R可以取1e-4到1e-2量级。然后Q取一个对角阵,对角线数值反映你对模型的信心。Q设置太小,滤波器会过度相信模型,量测的新信息得不到利用,导致状态跟踪滞后;Q设置太大,滤波器会过度相信量测,估计值会出现明显抖动。

初期调参时,我习惯把R固定,从一个小Q开始逐步增大,观察估计曲线的平滑度和跟随性,找到折中的位置。还有一个实用技巧:如果滤波结果出现持续偏差且协方差收敛得过小,多半是Q偏小;如果结果剧烈抖动,多半是Q偏大或者R偏小。

3.3 UKF核心循环的Matlab实现

UKF的代码实现是重点。先要生成sigma点,然后传播,再重构统计量。我把核心循环贴出来,这段代码可以直接嵌入你的工程:

function [x_est, P_est] = ukf_predict_update(x_est, P_est, z, dt, param, Wm, Wc, n, lambda) % 生成sigma点 S = chol((n + lambda) * P_est, 'lower'); X = zeros(n, 2 * n + 1); X(:, 1) = x_est; for i = 1:n X(:, i + 1) = x_est + S(:, i); X(:, i + 1 + n) = x_est - S(:, i); end % 时间更新:sigma点穿过状态方程 X_pred = zeros(n, 2 * n + 1); for i = 1:2 * n + 1 X_pred(:, i) = f_sys(X(:, i), dt, param); end xp = sum(Wm .* X_pred, 2); Pp = zeros(n, n); for i = 1:2 * n + 1 d = X_pred(:, i) - xp; Pp = Pp + Wc(i) * (d * d'); end Pp = Pp + param.Q; % 量测更新 Y = zeros(1, 2 * n + 1); for i = 1:2 * n + 1 Y(i) = h_meas(X_pred(:, i), param); end yp = sum(Wm .* Y, 2); Pxy = zeros(n, 1); Pyy = 0; for i = 1:2 * n + 1 dx = X_pred(:, i) - xp; dy = Y(i) - yp; Pxy = Pxy + Wc(i) * (dx * dy); Pyy = Pyy + Wc(i) * (dy * dy); end Pyy = Pyy + param.R; K = Pxy / Pyy; x_est = xp + K * (z - yp); P_est = Pp - K * Pxy'; end

这段代码里,chol分解用于求协方差矩阵的平方根,拿到它才能构造sigma点。这里有个实际坑:如果P_est因为数值误差变得不正定,chol会报错。我的处理方式是加一层try-catch,捕获分解失败时对P_est做对称化加对角扰动处理,保证程序不崩。

权重Wm和Wc按照标准公式在主函数中预先计算:

alpha = 1e-3; beta = 2; kappa = 0; n = 2; 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); for i = 1:n Wm(i + 1) = 1 / (2 * (n + lambda)); Wc(i + 1) = Wm(i + 1); Wm(i + 1 + n) = 1 / (2 * (n + lambda)); Wc(i + 1 + n) = Wm(i + 1 + n); end

很多同学不理解为什么Wc(1)比Wm(1)多了一项(1-alpha^2+beta)。这个修正项的作用是对sigma点采样引入的方差损失做补偿,让变换后的协方差保持无偏。在标准高斯分布下,取beta=2能让这个修正达到最优。

3.4 EKF核心循环与雅可比计算

EKF的代码结构比UKF简单一些,问题在于雅可比矩阵怎么来。如果解析推导有困难,数值差分是一个务实的选择。我自己在验证阶段经常这么干:

function F = fd_dFdx(x, dt, param) n = length(x); ep = 1e-6; F = zeros(n, n); for i = 1:n xp = x; xp(i) = xp(i) + ep; xm = x; xm(i) = xm(i) - ep; F(:, i) = (f_sys(xp, dt, param) - f_sys(xm, dt, param)) / (2 * ep); end end

同样的方法可以得到量测矩阵H。这套差分雅可比代码通用性很强,你后期换任何状态方程都能直接用,不用改。

EKF主循环如下:

F = fd_dFdx(x_est, dt, param); xp = f_sys(x_est, dt, param); Pp = F * P_est * F' + param.Q; H = fd_dhdx(xp, param); K = Pp * H' / (H * Pp * H' + param.R); x_est = xp + K * (z - h_meas(xp, param)); P_est = (eye(n) - K * H) * Pp;

代码顺序要特别注意:雅可比矩阵F要在状态更新前基于当前估计值计算,而不是用更新后的状态。很多初学EKF的同学在这里把顺序搞反,结果滤波一直收敛不了。

数值差分的步长ep也不能乱取。取太大,线性化近似误差大;取太小,浮点舍入误差会淹没差分信号。一般取1e-6左右比较合适。你可以在不同步长下看雅可比矩阵数值是否稳定,再决定最终参数。

3.5 完整仿真算例的搭建流程

我把整个流程串成一个主脚本,方便你直接跑通。流程分四步:初始化参数、生成真值轨迹与量测、分别调用EKF和UKF、绘图对比。

仿真中我设置采样时间dt=0.01秒,仿真时长5秒,相当于500个滤波周期。初始状态设置为真值附近的小偏差,故意制造一个滤波器追赶过程,用来观察收敛性能。量测噪声的方差取一个适中的值,让两种算法都有发挥空间。

生成量测数据的方式是:先用真值状态代入量测方程,再加上高斯白噪声:

rng(2024); T_steps = 500; dt = 0.01; x_log = zeros(2, T_steps); x_log(:, 1) = [0.4; 0.995]; for k = 1:T_steps - 1 x_log(:, k + 1) = f_sys(x_log(:, k), dt, param); x_log(2, k + 1) = x_log(2, k + 1) + 0.001 * randn; % 过程噪声 end z_log = zeros(1, T_steps); for k = 1:T_steps z_log(k) = h_meas(x_log(:, k), param) + sqrt(param.R) * randn; end

注意生成真值轨迹时加的过程噪声,和滤波器中设置的Q要匹配。如果真值轨迹是平滑曲线而滤波器里Q很大,滤波器会“以为”模型不可靠,结果过度跟随测量,抖动加大。

4. 完整实操过程:仿真算例与结果解读

4.1 两种滤波器的运行效果比较

跑完仿真后,我把EKF和UKF的估计结果画在同一张图上,可以清楚看到几个特征。初始几步两种滤波器都快速向真值靠拢,说明初始化程序没问题。随后进入稳态跟踪阶段,EKF的误差波动略大,尤其是在功角变化速率较快的时段,线性化误差带来的影响会放大。UKF的误差曲线整体更平稳,没有明显的滞后或超调。

我用均方根误差RMSE来定量比较,计算公式很简单:

rmse_delta_ekf = sqrt(mean((x_ekf(1, :) - x_log(1, :)).^2)); rmse_delta_ukf = sqrt(mean((x_ukf(1, :) - x_log(1, :)).^2));

我的测试结果大致是UKF的RMSE比EKF小20%到40%,具体数值取决于你设置的初值偏差和噪声大小。但这并不意味着EKF完全没优势。在维度较低、量测更新较快、初始偏差不大的情况下,EKF的精度已经足够,而且每次迭代的时间明显更短。

4.2 协方差传播的区别

另一个值得看的角度是协方差。把两种滤波器估计的协方差对角线开方拿出来画标准差曲线,你会发现EKF的协方差通常更快收敛到较小的值,有时候甚至过于自信。这其实是EKF线性化带来的偏差——它低估了真实误差,因为一阶展开忽略了高阶项。UKF因为考虑了sigma点传播中的非线性特征,协方差估计更接近真实误差水平。

这意味着你在工程上使用EKF时,不能完全相信它给出的协方差,建议在协方差基础上留余量。而UKF的协方差用于构造置信区间、做异常检测时,可靠性更高。这也是我后来偏向用UKF做在线动态安全评估的原因之一。

4.3 故障扰动场景下的鲁棒性测试

为了模拟更接近实际的情况,我在仿真中加入了一个持续0.1秒的故障扰动,在这个区间内电磁功率突变,状态轨迹出现一个明显的摇摆。由于过程噪声无法完全描述这种大幅突变,滤波算法会在故障期间产生一段跟踪误差窗口。

EKF在这个场景下的劣势更明显,因为它的线性化点在故障前后偏离真实状态较远,雅可比失效概率大。UKF由于传播了多个sigma点,能覆盖更广的状态分布范围,故障后的恢复速度明显更快。这正好印证了第二章的结论:非线性越强,UKF的优势越大。

5. 常见问题与排查技巧实录

5.1 滤波器发散怎么办

发散是滤波算法最令人头大的问题。直观表现是估计值突然飞出去,或者误差持续增大然后再也回不来。我遇到过的发散原因大概有几类,对应不同解法。

第一,初始协方差P0设置过小,滤波器一上来就过于相信初值,后面量测再怎么拉也拉不回来。解决方法是把P0适当放大,比如设为状态量典型幅值平方的几倍。第二,过程噪声Q过小,模型误差没有被滤波器认知,导致增益过低。这时候把Q增大,让滤波器保持对量测的敏感。第三,雅可比矩阵算错,EKF里尤其常见。我习惯用数值差分雅可比对照检查解析值,跑几条轨迹对比误差曲线就能发现问题在哪。

还有一个容易被忽略的点:double check量测方程里三角函数的输入单位。功角到底是弧度还是角度,Matlab的sin函数只认弧度。如果单位混了,量测预测值会形成稳定的偏差,滤波结果看起来“有界但不准”。

5.2 协方差矩阵非正定怎么处理

UKF里需要个chol分解,EKF里也依赖协方差更新公式保持正定性。但数值计算中协方差矩阵会由于舍入误差、减法消除变得不对称甚至不正定。处理手段按实用优先级排:

首先,每次更新后强制对称化,把上三角或下三角复制成对称矩阵:

P_est = (P_est + P_est') / 2;

其次,如果chol还是失败,加一个小的对角扰动,比如P_est + 1e-12 * eye(n)。这招很简单但很有效。最后,更稳健的方案是采用平方根滤波形式,直接传播协方差的平方根因子Cholesky因子,从根上避免非正定问题。这种做法在数值极端情况下最可靠,代价是实现复杂度稍高。

5.3 参数alpha和beta的影响

UKF里的alpha非常敏感。alpha太小,sigma点离均值太近,非线性传播时高阶信息捕捉不到,协方差可能偏小;alpha太大,sigma点分布范围太宽,容易把非局部效应带进来,结果更容易发散。我测试下来,低维状态估计取1e-2到1e-3之间是比较稳的区间,高维问题可以适当调大一些。

beta取2基本是学术界共识,针对高斯分布是理论最优。如果你对先验分布的尾部有特殊要求,可以调整,但一般情况下不必动它。kappa在大多数场景下取0即可。

5.4 采样时间怎么选

采样时间对动态估计的影响比很多人以为的大。dt过大,离散化误差增加,模型预测精度下降,状态突变时跟踪跟不上;dt过小,计算量增加,而且量测噪声被放大,滤波器容易出现过度调节。

我习惯的做法是让dt和量测数据的时间分辨率保持一致,一般取0.01秒或0.02秒,对应50Hz或100Hz系统。如果你自己做的系统是30帧/秒的采集频率,就取dt=1/30,不要强行离散成10ms。滤波器的稳定性分析可以用CT模型来验证离散化是否合理。

5.5 多量测扩展需要注意的维度问题

上面的示例代码里量测是标量,方便理解。实际项目中PMU能够提供多个量测,比如电压幅值、相量、有功无功。这时h_meas的输出变成m维列向量,对应的Pyy从标量变成m×m矩阵,Pxy变成n×m矩阵,卡尔曼增益K变成n×m矩阵。

代码修改并不复杂,但有个容易踩的坑:量测噪声矩阵R必须是m×m对角阵,对角线是每个量测的噪声方差。如果量测间存在相关性,非对角元素不能简单置零。实际工程中PMU的不同量测通道误差通常是独立的,用对角阵问题不大,但如果你在融合SCADA和PMU异构数据时,R的构造要特别小心,不然会导致滤波更新权重失衡,误差反而放大。

最后的实操体会

两套算法都在Matlab里跑通之后,我最大的体会是:EKF和UKF并不存在绝对的谁取代谁,它们在不同场景下各有适用边界。EKF胜在原理简单、计算量小,适合对实时性要求高且工作点变化平缓的系统;UKF胜在无需求导、非线性适应性强,适合模型复杂、扰动频繁的在线估计场景。对你的项目来说,我建议不要只盯着算法本身,要把更多精力放在状态方程建模和噪声参数标定上,这两个环节对估计精度的影响往往比选EKF还是UKF更大。另外建议你把滤波代码和仿真脚本分开组织,状态方程、量测方程、滤波算法各自独立成函数,这样后续换模型、加约束、加坏数据检测模块都会很顺手。

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

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

立即咨询