
电力系统动态状态估计这几年在学术界和工程圈里都被频繁提起尤其是同步相量测量单元PMU大规模部署之后以前靠SCADA做稳态状态估计的思路正在被慢慢刷新。我最近在Matlab里完整实现了一套基于扩展卡尔曼滤波EKF和无迹卡尔曼滤波UKF的电力系统动态状态估计仿真从数学模型推导到代码落地再到滤波器参数整定踩了不少坑也积累了一些经验。这篇文章就把整个实现过程、核心原理、代码框架和排错心得完整梳理一遍适合正在做电力系统动态状态估计课题的研究生、做在线监测算法开发的工程师以及对EKF/UKF在非线性系统中的应用感兴趣的读者参考。如果你只需要跑通一个能出结果的仿真或者想搞清楚两种滤波器的本质差异和选型逻辑这篇文章都能给你一个可复现的起点。1. 项目概述与思路拆解1.1 为什么要做动态状态估计先说清楚一个容易被忽略的问题传统的电力系统状态估计无论是加权最小二乘还是快速分解法本质上都是基于断面量测的静态估计。SCADA系统刷新周期在秒级甚至分钟级只能给出稳态工况下的电压幅值、相角和潮流分布。但电力系统真正的动态过程——发电机转子摇摆、频率振荡、功角失稳——这些机电暂态现象的时间常数是几十毫秒到几秒量级SCADA根本采集不到足够密度的数据。PMU的出现改变了这个局面。它能够以30到60帧每秒的速率同步输出电压电流相量时间分辨率足够捕捉机电动态过程。但有了高密度量测之后传统静态状态估计的算法框架反而不适用了——静态模型里没有时间维度每个时刻的量测都是独立处理的完全没有利用系统的动态演化规律。动态状态估计Dynamic State Estimation, DSE的思路就是在状态估计的框架里引入发电机组的动态方程用“模型预测 量测更新”的递推方式实现对功角、转速等动态状态的实时跟踪。实际做下来你会发现DSE的核心难点有两个一是系统模型是强非线性的发电机摇摆方程里含有sin(delta)这样的项经典卡尔曼滤波的线性假设根本不成立二是PMU量测噪声特性和SCADA不一样误差统计规律不同对滤波器的鲁棒性要求更高。这就自然引出了EKF和UKF这两种非线性滤波方案的选择问题。1.2 EKF与UKF选型的基本逻辑EKF的思路很直接既然系统是非线性的那就把非线性函数在当前状态估计值附近做一阶泰勒展开用雅可比矩阵代替线性卡尔曼滤波里的状态转移矩阵和量测矩阵。这个方案实现简单、计算量小在工程里用了很多年是处理非线性估计问题的“默认选项”。UKF的思路则是从概率分布传播的角度切入。它不去线性化非线性函数而是通过确定性采样生成一组Sigma点让这些点通过非线性函数传播再用传播后的加权统计量近似状态的后验均值和协方差。对于非线性程度较高的系统UKF避免了泰勒展开的截断误差理论上能达到二阶以上的近似精度而EKF严格来说是只有一阶精度。但“理论上更优”并不等于“实际应用中无条件选UKF”。我在做这个项目的时候对比下来两者各有取舍EKF计算速度快但强非线性场景下可能出现估计偏差甚至发散UKF估计精度高、对初始值不敏感但计算量大约是EKF的2到3倍而且Sigma点采样过程中协方差矩阵容易失去正定性数值稳定性问题更突出。到底选哪一个取决于你的量测噪声水平、非线性程度和实时性要求。这也是为什么我在仿真框架里同时实现了两种滤波器——只有放在同一套系统里跑过对比你才能体会到这些差异是真实的还是理论上的。2. 数学模型与核心原理2.1 单机无穷大系统的动态模型与离散化做动态状态估计仿真我推荐从单机无穷大SMIB系统入手。原因很简单模型维度低、物理含义清晰、公式可以手推方便把滤波器的每个环节都验证清楚。等你在SMIB上把EKF和UKF的行为摸透了再往多机系统扩展也只是增加状态维度和量测维度的问题算法框架不需要变。我用的经典二阶模型包含两个状态发电机转子功角delta和转子角速度omega。微分方程如下d(delta)/dt omega - omega_sd(omega)/dt (Pm - Pe - D * (omega - omega_s)) / M其中omega_s是同步角速度工频50Hz对应约314.159 rad/sPm是机械功率Pe是发电机输出电磁功率D是阻尼系数M是惯性时间常数数值上等于2HH为惯性常数单位秒。在SMIB系统中电磁功率Pe是功角的非线性函数Pe (E * Vb / X) * sin(delta)E是发电机暂态电动势Vb是无穷大母线电压幅值X是发电机暂态电抗和线路电抗的等效串联值。这个式子里的sin(delta)就是整个系统非线性的核心来源。仿真中需要把连续微分方程离散化。我采用的是欧拉法取采样周期dt0.01秒对应100Hz的估计速率和实际PMU的报送频率30~60Hz在同一量级delta(k1) delta(k) (omega(k) - omega_s) * dtomega(k1) omega(k) dt * (Pm - Pe(k) - D * (omega(k) - omega_s)) / M这里的Pe(k)要代入sin(delta(k))计算所以状态转移函数f(x)本身就是非线性的。系统参数我设置如下H5秒M10D1E1.05标幺值Vb1.0标幺值X0.5标幺值Pm0.8标幺值。在这些参数下稳态功角约等于asin(0.8*0.5/1.05)大约22.4度量测Pe的稳态值就是0.8。这个工作点留了一些裕度后续施加扰动时功角不会越过失稳边界保证仿真过程稳定。2.2 EKF的核心推导与局限EKF的每一轮递推分两步预测和更新。预测步基于状态转移函数计算先验估计x_pred f(x_prev) P_pred F * P_prev * F Q其中F是状态转移函数对状态向量x的雅可比矩阵。对这个二阶模型手推可以得到F [1, dt; -dt * (E * Vb / X) * cos(delta) / M, 1 - D * dt / M]注意右上角元素是dt左上角是1左下角含有cos(delta)项右下角是1 - D*dt/M。每一轮预测时左下角都要用当前的delta值重新计算因为F严格依赖于当前工作点。更新步利用量测函数h(x) (E * Vb / X) * sin(delta)对应雅可比矩阵H [(E * Vb / X) * cos(delta), 0]然后计算卡尔曼增益、后验状态和协方差K P_pred * H / (H * P_pred * H R) x_est x_pred K * (z - h(x_pred)) P_est (I - K * H) * P_pred整套推导的数学过程不复杂但EKF的问题也恰恰出在这个“推导”上。第一当非线性函数展开点离真实状态较远时一阶泰勒展开的截断误差会通过增益矩阵放大造成估计偏离第二雅可比矩阵需要人工推导对于更高阶的发电机模型比如四阶或六阶模型或者带饱和限幅的励磁系统手推F和H非常痛苦稍不小心就推错第三EKF假设展开后的线性化误差是高斯分布这个假设在强非线性环境下站不住脚。2.3 UKF的Sigma点传播原理UKF的核心思想我习惯用一个类比来解释EKF是把非线性函数“掰直”了再用高斯分布去套UKF则是把高斯分布“掰弯”了去贴合非线性函数的形状。具体做法是在当前状态均值x周围按照协方差P的分布特征确定性选取2n1个Sigma点n为状态维度然后让每个Sigma点独立地通过非线性函数传播最后用这些点的加权均值和加权协方差来近似传播后的分布统计量。Sigma点生成公式为X0 x Xi x sqrt((n lambda) * P) 的列i1到n Xim x - sqrt((n lambda) * P) 的列in1到2nlambda alpha^2 * (n kappa) - nalpha通常取1e-3到1kappa通常取0或3-nbeta针对高斯分布取2。对应的均值权重和协方差权重分别为Wm0 lambda / (n lambda) Wc0 lambda / (n lambda) (1 - alpha^2 beta) Wm_i Wc_i 1 / (2*(n lambda))i1到2nSigma点生成的核心操作是协方差矩阵的Cholesky分解也就是P S * S然后用sqrt(nlambda)放大S的每一列加到均值两侧。这也是UKF最容易出数值问题的地方——只要P出现微小的不正定Cholesky分解就直接报错滤波器当场罢工。后面我会专门讲这个问题怎么处理。3. Matlab仿真框架与代码实现3.1 整体程序结构设计我在Matlab里把整个仿真拆成了六个模块每个模块一个脚本或函数方便单独调试参数初始化脚本定义系统参数、采样周期、仿真时长、噪声协方差真实系统仿真函数生成“真值”轨迹和量测数据EKF实现函数输入上一时刻估计值和量测输出当前估计值UKF实现函数输入上一时刻估计值和量测输出当前估计值主循环脚本按时间步调用两种滤波器记录估计误差统计RMSE绘图脚本对比真实状态、估计状态和误差曲线这种结构的最大好处是可以单独替换滤波器模块。如果你想换一种新算法做对比比如粒子滤波或者平方根UKF只需要实现一个接口一模一样的函数即可主循环和绘图逻辑完全不用改。主循环的伪代码如下for k 1:N % 生成当前时刻量测真值加噪声 z(:,k) h(x_true(:,k)) sqrt(R) * randn(1); % EKF一步递推 [x_ekf(:,k1), P_ekf] ekf_update(x_ekf(:,k), P_ekf, z(:,k), params); % UKF一步递推 [x_ukf(:,k1), P_ukf] ukf_update(x_ukf(:,k), P_ukf, z(:,k), params); end3.2 EKF的实现细节EKF实现是整个项目里最直白但也最需要小心的部分。直白在于公式固定小心在于雅可比矩阵既要对又要高效。我在代码里直接写解析雅可比而不是用有限差分去逼近因为对于这个二阶模型解析式推导很干净而且避免了差分步长选择的麻烦。function [x_est, P_est] ekf_update(x_prev, P_prev, z, prm) % 参数解包 dt prm.dt; ws prm.ws; Pm prm.Pm; D prm.D; M prm.M; E prm.E; Vb prm.Vb; X prm.X; Q prm.Q; R prm.R; delta x_prev(1); omega x_prev(2); Pe (E*Vb/X) * sin(delta); % 预测步 x_pred [delta (omega - ws) * dt; omega dt * (Pm - Pe - D*(omega - ws)) / M]; F [1, dt; -dt*(E*Vb/X)*cos(delta)/M, 1 - D*dt/M]; P_pred F * P_prev * F Q; % 更新步 H [(E*Vb/X) * cos(x_pred(1)), 0]; S H * P_pred * H R; K (P_pred * H) / S; x_est x_pred K * (z - (E*Vb/X)*sin(x_pred(1))); P_est (eye(2) - K*H) * P_pred; end有一个细节容易忽略H矩阵里的cos项在预测步用的是x_pred(1)还是x_prev(1)严格来说应该用展开点也就是预测值x_pred因为H在计算卡尔曼增益时代表的是量测函数在“当前工作点”的局部斜率。但很多教材里写EKF时两种取法都出现过我在代码里统一用x_pred(1)这样更新步的线性化点和预测协方差的传播点保持一致逻辑上更严谨。3.3 UKF的实现细节UKF实现比EKF多两步生成Sigma点和传播后的统计量聚合。我保留了alpha、beta、kappa三个参数作为可调项方便做敏感性分析。function [x_est, P_est] ukf_update(x_prev, P_prev, z, prm) n numel(x_prev); dt prm.dt; ws prm.ws; Pm prm.Pm; D prm.D; M prm.M; E prm.E; Vb prm.Vb; X prm.X; Q prm.Q; R prm.R; % 状态转移函数 f (x) [x(1) (x(2)-ws)*dt; x(2) dt*(Pm - (E*Vb/X)*sin(x(1)) - D*(x(2)-ws))/M]; % 量测函数 h (x) (E*Vb/X) * sin(x(1)); % 参数与权重 alpha prm.alpha; beta prm.beta; kappa prm.kappa; lambda alpha^2 * (n kappa) - n; Wm [lambda/(nlambda), repmat(1/(2*(nlambda)), 1, 2*n)]; Wc Wm; Wc(1) Wm(1) (1 - alpha^2 beta); % 生成Sigma点 S chol(P_prev, lower); X_sigma [x_prev, x_prev sqrt(nlambda)*S, x_prev - sqrt(nlambda)*S]; % 状态传播 X_pred zeros(n, 2*n1); for i 1:(2*n1) X_pred(:,i) f(X_sigma(:,i)); end x_pred X_pred * Wm; P_pred Q; for i 1:(2*n1) dx X_pred(:,i) - x_pred; P_pred P_pred Wc(i) * (dx * dx); end % 量测更新 Z zeros(1, 2*n1); for i 1:(2*n1) Z(i) h(X_pred(:,i)); end z_pred Z * Wm; Pzz R; Pxz zeros(n, 1); for i 1:(2*n1) dz Z(i) - z_pred; dx X_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 - z_pred); P_est P_pred - K * Pzz * K; end这里有两个实战细节想提醒大家。第一Cholesky分解要求P_prev严格正定如果滤波器跑了几步之后P_prev出现非正定chol会直接报错。我通常在函数入口先对P_prev做一次特征值检查如果有非正特征值就加一个很小的对角阵再分解相当于给协方差矩阵一个“数值保护垫”。第二Sigma点的顺序不能乱——第一列必须是当前状态均值后面先加后减对称排列这个约定会影响权重的对应关系写错了滤波器会发散到让你怀疑人生。量测函数h在Sigma点传播时要对整个状态预测分布的所有Sigma点都调用一遍不能用单个点的线性化替代这是UKF和EKF最本质的区别。UKF之所以能在强非线性下保持精度就是因为每个Sigma点都真实地经过了非线性函数而不是在展开点附近被“掰直”。3.4 仿真场景设计与扰动设置静态运行工况下滤波器的状态一直稳定在真实值附近两种算法看不出明显差距所以我在仿真里设置了一个动态扰动场景前5秒系统稳定在Pm0.8的工作点第5秒时机械功率阶跃到0.85。这个扰动会激起功角和转速的动态过渡过程滤波器此时必须依靠动态模型和量测信息协同工作才能跟上真实轨迹——这才是体现EKF和UKF差异的关键场景。具体参数设置如下参数数值说明惯性常数 H5 sM 2H 10阻尼系数 D1标幺值暂态电动势 E1.05标幺值母线电压 Vb1.0标幺值等效电抗 X0.5标幺值初始机械功率0.8标幺值扰动后机械功率0.85第5秒阶跃采样周期 dt0.01 s100 Hz量测噪声标准差sqrt(1e-4)Pe量测噪声Q矩阵diag([1e-5, 1e-4])过程噪声协方差初值协方差 P0diag([1e-2, 1e-2])初始不确定度量测噪声标准差取0.01标幺值相对Pe的稳态值0.8来说大约是1.2%的噪声水平这个信噪比在实际PMU量测中不算苛刻也不算理想刚好能看出两种滤波器的差异。4. 仿真结果与性能对比分析4.1 滤波精度对比跑完10秒仿真第5秒施加扰动后两种滤波器都能跟踪上真实状态但细节差异很明显。功角估计的RMSE方面EKF大致在0.02到0.03弧度水平UKF可以压到0.01到0.015弧度UKF的均方根误差大概能比EKF小30%到50%。转速估计的差距类似UKF在扰动阶段的超调和滞后明显更小。这个差别的机理可以从Sigma点传播的角度解释。在扰动后的动态过程中功角在短时间内发生明显偏移sin(delta)在这个区间内的非线性程度比较强。EKF在每一时刻用一个局部线性近似替代真实非线性函数当状态变化速度较快时线性化点跟不上真值的变化误差会在连续几个时间步内累积。UKF的5个Sigma点在状态分布范围内覆盖了非线性函数的曲率信息传播后的均值更贴近真实分布的中心所以偏差更小。我建议你做实验时不要只比较稳态段要把扰动发生后的3到5秒单独切出来统计RMSE。两种滤波器在稳态段的表现很接近差距主要出现在动态段只看全程平均值会掩盖这个关键差异。4.2 计算效率与实时性分析UKF的精度优势不是免费的。在我的二阶状态模型里每个时间步需要5次状态转移函数和5次量测函数求值加上Sigma点生成和权重聚合的开销。EKF需要1次状态转移、1次量测函数求值再加上解析雅可比的计算。在Matlab里实测下来UKF单步耗时大约是EKF的2到3倍。对于二阶模型来说这个耗时差异完全在可接受范围内——在普通笔记本上10秒仿真、1000个时间步两种滤波器总耗时都在几十毫秒量级实时性都不会成为瓶颈。但如果你以后把模型升级到多机系统比如10台发电机就是20个状态UKF的Sigma点数会到41个计算量快速增长。到那个时候EKF、UKF和粒子滤波之间的取舍就需要重新做量化评估了。工程上还有一个折中方案值得关注自适应无迹卡尔曼滤波AUKF和平方根UKF。平方根UKF直接对P的Cholesky因子做递推更新避免了每步重新分解的开销数值稳定性也更好。如果你的应用场景对实时性有硬约束可以考虑在UKF框架内做平方根改造。5. 常见问题与排错经验实录5.1 滤波器发散问题仿真过程中我遇到的最常见问题是滤波器发散。现象很典型前几百步估计还算正常突然某个时刻功角估计值跳到几十度甚至上百度的离谱值之后再也回不来。我排查下来主要原因有三类初始协方差P0设得太小、过程噪声Q设得太小、以及更新步里除零或接近奇异。P0太小意味着滤波器对初始状态“过分自信”当真实初始状态和你的设定值有偏差时滤波器没有能力在后续观测中快速修正。解决办法是把P0放宽。Q太校小意味着滤波器过度信任模型预测一旦发生模型失配或者扰动量测修正被过小的增益压制估计值就会慢慢漂移。碰到发散问题我的排查顺序是打印每一轮的预测残差、协方差矩阵对角线元素、卡尔曼增益看是哪个环节先出现异常。如果你的滤波器在某些时间步直接报NaN那多半是数值问题而不是逻辑问题。检查P矩阵是否有负的对角线元素检查卡尔曼增益公式中的分母是否接近零检查量测残差是否为NaN。5.2 Q/R矩阵整定的经验法则Q和R的取值是滤波器表现好坏的决定性因素。我的经验是分两步走先用物理量级估算再按仿真结果微调。量测噪声R可以直接从PMU的技术参数里得到比如幅值量测误差的方差过程噪声Q则要反映模型误差和未知扰动的大小通常拿“等效的不确定性功率扰动”来折算再按离散化时间步长按比例缩放。初始Q值和R值定好之后我会用一个“信心比例”来调优如果滤波器的估计曲线太光滑、对量测的跟踪太迟钝说明Q相对R偏小加大Q如果估计曲线抖动剧烈、跟着量测噪声走说明Q相对R偏大减小Q。实际调整中Q和R同时放大或缩小同样的倍数滤波器输出几乎不变因为增益取决于两者的比例而不是绝对大小。知道这一点可以帮你避免白费功夫。5.3 UKF协方差非正定的处理技巧我前面提到过Cholesky分解容易碰到协方差非正定的问题。这在UKF里尤其常见因为Sigma点传播过程中数值舍入误差和权重聚合的四舍五入都可能让P_pred从半正定变成带轻微负特征值。如果不处理chol直接报错。我的处理技巧有三个层次。第一优先是修正算法本身比如在传播聚合时用dx*dx的对称化写法避免不对称误差累积。第二是加数值保护在分解前检查最小特征值如果小于某个阈值比如1e-12就加一个小的单位阵倍数。第三种更彻底的做法是改用平方根UKF直接对Cholesky因子做QR分解递推从根上避免分解失败。这里特别提醒一个依赖问题如果P矩阵的病态性特别严重单纯加小对角阵可能掩盖问题。你应该同时检查是不是某个状态的方差在连续多步里被压得过小——这通常意味着该状态的量测更新信息过多滤波器产生了“虚假自信”。6. 扩展方向与个人体会这套EKF和UKF仿真框架的扩展空间很大。最直接的扩展是把单机无穷大系统换成多机系统状态维度从2维升到几十维量测也相应增加算法主体不用动。另一个方向是换成更精细的发电机模型比如考虑励磁动态的六阶模型这时候EKF的雅可比矩阵推导会变得很烦人而UKF完全不需要雅可比这一优势就凸显出来了——只需要修改状态转移函数和量测函数Sigma点框架本身不用做任何改动。还可以考虑把输入配网和主动配电网场景纳入进来分布式电源逆变器的控制动态也是典型的强非线性过程EKF和UKF都在这个领域有大量应用。如果你对鲁棒性有更高要求可以在UKF基础上引入H无穷滤波思想或者用自适应策略在线辨识Q和R矩阵。最后分享一点个人体会。从我踩过的坑来看做非线性滤波器仿真最花时间的地方往往不是算法本身而是系统建模和参数整定。很多初学者拿来一套代码就跑跑出来结果不合理就开始怀疑算法有问题其实八成是系统参数或者噪声协方差设置得不合理。我的建议是拿到任何滤波器代码第一件事先关掉量测噪声跑一遍开环仿真确认状态转移函数本身是稳定且正确的然后打开量测噪声把R设得很大确认滤波器基本依赖模型预测最后逐步收窄R观察估计值从“跟随模型”平滑过渡到“跟随量测”。这个三步走的过程走完你对滤波器行为的理解会完全上一个台阶。这套代码我建议你拿去做参数敏感性分析把alpha从1e-3改到0.5看看UKF的精度变化把扰动幅值加大看看EKF什么时候开始力不从心。每组实验都输出RMSE表格你会对两种算法的适用边界有一个非常直观的认知——这种认知是读再多的论文也换不来的。