论文解读
来自:《基于改进无迹卡尔曼滤波的锂电池SOC估计方法》
基于改进无迹卡尔曼滤波的锂电池SOC估计方法
本文解读的算法来自论文《基于改进无迹卡尔曼滤波的锂电池SOC估计方法》。该工作提出了一种融合鹦鹉优化算法(Parrot Optimizer, PO)与H∞\text{H}_\inftyH∞鲁棒准则的新型滤波器------PO-RUKF,用于高精度、自适应地估计锂离子电池的荷电状态(SOC)。
给你的手机/电动车"读心术":如何用"鹦鹉优化"的卡尔曼滤波,精准预测电池还剩多少电?
从安时积分到PO-RUKF,一篇让你看懂SOC估计黑科技的保姆级教程
你有没有过这样的经历?
- 手机显示还有20%的电,结果下一秒就自动关机了?
- 电动车仪表盘上明明还有100公里续航,开出去没多久就亮起了"龟速"红灯?
这背后,都是因为一个看似简单、实则极其复杂的工程难题:如何精确知道一块锂电池当前的"荷电状态"(State of Charge, SOC)?
SOC就像是电池的"油表",但它比汽车油箱复杂得多。你不能直接"看"到电池内部的电子储量,而且电池性能会随着温度、老化程度、充放电倍率动态变化。想要精准估算SOC,既要懂电池的物理化学特性,又要会用智能算法------这是一个典型的"交叉学科难题"。
第一幕:传统方法为何频频"翻车"?
先来看看工业界和学术界早期的SOC估计方法,以及它们各自的"致命痛点"。
1. 安时积分法(Coulomb Counting)------ "粗心的记账员"
这是最直观、最常用的方法,原理等同于"记账":记录每一分每一秒的电流,累加计算总电量变化。
- 核心公式 :
SOC(t)=SOC0−ηCn×3600∫0tI(τ)dτ \text{SOC}(t) = \text{SOC}_0 - \frac{\eta}{C_n \times 3600} \int_0^t I(\tau)d\tau SOC(t)=SOC0−Cn×3600η∫0tI(τ)dτ
其中,SOC0\text{SOC}_0SOC0是初始SOC,η\etaη是库伦效率,CnC_nCn是电池额定容量(Ah),I(τ)I(\tau)I(τ)是充放电电流(放电为正,充电为负)。 - 优点:计算简单、实时性强,嵌入式芯片也能轻松运行。
- 致命问题 :
- 累积误差:电流传感器的微小误差会随时间不断放大,几小时后SOC估计就会严重偏离真实值;
- 初始值依赖 :必须精准知道SOC0\text{SOC}_0SOC0,否则从一开始就错了;
- 容量衰减:电池老化后实际容量会下降,但算法通常沿用额定容量,进一步加剧误差。
2. 开路电压法(OCV Method)------ "只能冷静时工作的观察员"
这个方法利用了电池的核心物理特性:完全静置后的开路电压(OCV)与SOC呈一一对应的函数关系。
- 操作流程:让电池静置1~2小时(消除极化效应)→ 测量OCV → 查预设的"OCV-SOC"曲线表 → 得到SOC。
- 优点:精度高(实验室条件下误差可小于1%),无需复杂计算。
- 致命问题 :动态场景完全失效 !
电动车行驶、手机玩游戏时,电池处于高频充放电状态,端电压受极化电压、欧姆压降影响极大,此时测量的电压和OCV毫无关系。这个方法只能用于"离线校准",无法实时估计。
显然,我们需要一个既能实时计算,又能自我修正误差 的智能方法。这时候,大名鼎鼎的卡尔曼滤波(Kalman Filter) 就登场了!
第二幕:卡尔曼滤波------那个"猜得越来越准"的天才算法
卡尔曼滤波被誉为"20世纪最伟大的算法之一",它的核心思想不是"猜",而是**"基于模型预测+基于传感器修正"的最优融合**:
"我既相信电池模型算出的预测值,也相信电压传感器的测量值,但我会根据两者的可信度,动态分配权重,得到最靠谱的结果。"
对于锂电池这种强非线性系统 ,线性卡尔曼滤波(KALF)效果很差,于是它的"升级版"------无迹卡尔曼滤波(UKF) 成为了SOC估计的主流算法。
UKF的核心魔法:用"Sigma点"破解非线性难题
UKF的精妙之处在于:不直接对非线性函数做近似,而是用一组精心挑选的采样点(Sigma点)来近似状态的概率分布。
举个通俗的例子:
你要预测一个不规则气球在风中的运动轨迹(非线性),不需要解复杂的流体力学方程,只需要在气球表面贴几个标记点(Sigma点),跟踪这些点的运动,就能推断出整个气球的轨迹。
UKF估计SOC的关键步骤(四步走)
我们定义电池的状态向量为 x=[SOC, Vp1, Vp2]T\boldsymbol{x} = [\text{SOC},\ V_{p1},\ V_{p2}]^Tx=[SOC, Vp1, Vp2]T(包含SOC和两个极化电压,对应二阶RC模型),观测向量为端电压 VVV。
- 步骤1:生成Sigma点
围绕当前最优估计值 x^k−1∣k−1\hat{\boldsymbol{x}}{k-1|k-1}x^k−1∣k−1,生成 2n+12n+12n+1 个Sigma点(nnn是状态维度,这里n=3n=3n=3,共7个点):
χk−1(i)={x^k−1∣k−1,i=0x^k−1∣k−1+(n+λ)Pk−1∣k−1i,i=1,2,...,nx^k−1∣k−1−(n+λ)Pk−1∣k−1i,i=n+1,...,2n \chi{k-1}^{(i)} = \begin{cases} \hat{\boldsymbol{x}}{k-1|k-1}, & i=0 \\ \hat{\boldsymbol{x}}{k-1|k-1} + \sqrt{(n+\lambda) \boldsymbol{P}{k-1|k-1}}i, & i=1,2,...,n \\ \hat{\boldsymbol{x}}{k-1|k-1} - \sqrt{(n+\lambda) \boldsymbol{P}{k-1|k-1}}_i, & i=n+1,...,2n \end{cases} χk−1(i)=⎩ ⎨ ⎧x^k−1∣k−1,x^k−1∣k−1+(n+λ)Pk−1∣k−1 i,x^k−1∣k−1−(n+λ)Pk−1∣k−1 i,i=0i=1,2,...,ni=n+1,...,2n
其中,λ\lambdaλ是缩放系数,P\boldsymbol{P}P是协方差矩阵(表示状态的不确定性)。
解读 这个公式就是 UKF 的**"撒侦察兵"大法**!咱们用"猜电池状态"的例子,把它掰扯得明明白白:
核心主角
- x^k−1∣k−1\hat{\boldsymbol{x}}_{k-1|k-1}x^k−1∣k−1:你上一时刻最靠谱的"猜测结果",比如猜电池状态是 [SOC=0.8,Vp1=0.02V,Vp2=0.005V][SOC=0.8, Vp1=0.02V, Vp2=0.005V][SOC=0.8,Vp1=0.02V,Vp2=0.005V],这就是侦察兵的大本营。
- n=3n=3n=3:状态维度是3(SOC+两个极化电压),所以要派 2×3+1=72×3+1=72×3+1=7 个侦察兵。
协方差矩阵 P\boldsymbol{P}P 的作用
P\boldsymbol{P}P 就是**"不确定范围地图"**------P\boldsymbol{P}P 里的数值越大,说明你对这个状态的猜测越没底。比如 SOC 的不确定性大,地图上 SOC 对应的方向就越宽。(n+λ)P\sqrt{(n+\lambda)\boldsymbol{P}}(n+λ)P 的魔法
这一步是把"不规则的不确定地图"拉成方方正正的坐标轴 (就像把椭圆操场压成矩形跑道),方便侦察兵按方向出发。λ\lambdaλ 是个"缩放调节器",能控制侦察兵的出发距离------λ\lambdaλ 越大,侦察兵跑得越远,探索范围越广。
7个侦察兵的出发路线
- i=0i=0i=0:1个侦察兵留守大本营,不挪窝,代表最基础的猜测;
- i=1,2,3i=1,2,3i=1,2,3:3个侦察兵分别沿着3个坐标轴正方向跑一步,探索"状态偏高"的情况;
- i=4,5,6i=4,5,6i=4,5,6:另外3个侦察兵分别沿着3个坐标轴反方向跑一步,探索"状态偏低"的情况。
最后这7个侦察兵,就带着你的猜测,去电池模型里"走一趟",帮你摸清所有可能的状态------比硬解复杂方程轻松多啦!
代码对应:
python
n = x.shape[0]
lam = alpha**2 * (n + kappa) - n # alpha,kappa是UKF参数
sqrt_term = np.linalg.cholesky((n + lam) * P)
sigma = np.tile(x, (2*n+1, 1))
for i in range(n):
sigma[i+1] += sqrt_term[:, i] # 正方向采样
sigma[i+1+n] -= sqrt_term[:, i] # 负方向采样
- 步骤2:时间更新(预测)
让每个Sigma点通过电池非线性状态方程 ,得到预测的Sigma点 χk∣k−1(i)\chi_{k|k-1}^{(i)}χk∣k−1(i),再加权平均得到先验状态估计 x^k∣k−1\hat{\boldsymbol{x}}{k|k-1}x^k∣k−1 和先验协方差 Pk∣k−1\boldsymbol{P}{k|k-1}Pk∣k−1:
x^k∣k−1=∑i=02nWm(i)χk∣k−1(i) \hat{\boldsymbol{x}}{k|k-1} = \sum{i=0}^{2n} W_m^{(i)} \chi_{k|k-1}^{(i)} x^k∣k−1=i=0∑2nWm(i)χk∣k−1(i)
Pk∣k−1=∑i=02nWc(i)(χk∣k−1(i)−x^k∣k−1)(χk∣k−1(i)−x^k∣k−1)T+Q \boldsymbol{P}{k|k-1} = \sum{i=0}^{2n} W_c^{(i)} \left( \chi_{k|k-1}^{(i)} - \hat{\boldsymbol{x}}{k|k-1} \right) \left( \chi{k|k-1}^{(i)} - \hat{\boldsymbol{x}}_{k|k-1} \right)^T + \boldsymbol{Q} Pk∣k−1=i=0∑2nWc(i)(χk∣k−1(i)−x^k∣k−1)(χk∣k−1(i)−x^k∣k−1)T+Q
其中,Wm(i)W_m^{(i)}Wm(i)和Wc(i)W_c^{(i)}Wc(i)是Sigma点的权重,Q\boldsymbol{Q}Q是过程噪声协方差。
解读 这一步就是让咱们派出去的 7个侦察兵(Sigma点)去执行任务,再把它们的反馈汇总成"作战地图"!
第一步:侦察兵去前线(Sigma点传播)
每个Sigma点 χk−1(i)\chi_{k-1}^{(i)}χk−1(i) 都要钻进电池非线性状态方程 里"走一遭"------这个方程就像电池的"真实世界规则",侦察兵进去后,会根据当前的电流、时间,变成下一时刻的预测状态 χk∣k−1(i)\chi_{k|k-1}^{(i)}χk∣k−1(i)。
比如留守大本营的侦察兵(i=0i=0i=0),进去后会变成"按上一时刻最优猜测算出来的下一时刻状态";跑出去的侦察兵,会变成"状态偏高/偏低时,下一时刻可能的样子"。
第二步:给侦察兵的反馈打分(加权平均)
公式里的 Wm(i)W_m^{(i)}Wm(i) 是**"信任分"**------不是所有侦察兵的话都同等可信!
- 留守大本营的侦察兵(i=0i=0i=0)通常权重最高,因为它代表最靠谱的初始猜测;
- 跑出去的侦察兵权重较低,毕竟它们探索的是"边缘情况"。
把每个侦察兵的预测状态 χk∣k−1(i)\chi_{k|k-1}^{(i)}χk∣k−1(i) 乘以对应的信任分,再加起来,就得到了 先验状态估计 x^k∣k−1\hat{\boldsymbol{x}}_{k|k-1}x^k∣k−1 ------这就是"综合所有侦察兵情报后,猜出来的下一时刻电池状态"。第三步:画误差范围(计算先验协方差 Pk∣k−1\boldsymbol{P}_{k|k-1}Pk∣k−1)
光有猜测还不够,得知道这个猜测"有多靠谱"!
- (χk∣k−1(i)−x^k∣k−1)\left( \chi_{k|k-1}^{(i)} - \hat{\boldsymbol{x}}_{k|k-1} \right)(χk∣k−1(i)−x^k∣k−1) 就是每个侦察兵的反馈和综合猜测的差距------差距越大,说明猜测的不确定性越高;
- Wc(i)W_c^{(i)}Wc(i) 是计算不确定性时的"信任分"(和 Wm(i)W_m^{(i)}Wm(i) 类似,但数值略有不同);
- 最后加的 Q\boldsymbol{Q}Q 是**"意外风险值"**------代表电池模型本身的误差(比如电池老化、温度变化这些没考虑到的因素),就像侦察兵执行任务时遇到的"突发天气"。
整个公式算出来的 Pk∣k−1\boldsymbol{P}_{k|k-1}Pk∣k−1,就是"这次预测的误差范围地图"------地图上的数值越大,说明预测越没底。
- 步骤3:观测更新(预测端电压)
把先验Sigma点通过观测方程 (由RC模型计算端电压),得到预测的观测点 Υk∣k−1(i)\Upsilon_{k|k-1}^{(i)}Υk∣k−1(i),再加权平均得到预测端电压 V^k∣k−1\hat{V}{k|k-1}V^k∣k−1 和观测协方差 Pzz\boldsymbol{P}{zz}Pzz:
V^k∣k−1=∑i=02nWm(i)Υk∣k−1(i) \hat{V}{k|k-1} = \sum{i=0}^{2n} W_m^{(i)} \Upsilon_{k|k-1}^{(i)} V^k∣k−1=i=0∑2nWm(i)Υk∣k−1(i)
Pzz=∑i=02nWc(i)(Υk∣k−1(i)−V^k∣k−1)(Υk∣k−1(i)−V^k∣k−1)T+R \boldsymbol{P}{zz} = \sum{i=0}^{2n} W_c^{(i)} \left( \Upsilon_{k|k-1}^{(i)} - \hat{V}{k|k-1} \right) \left( \Upsilon{k|k-1}^{(i)} - \hat{V}_{k|k-1} \right)^T + \boldsymbol{R} Pzz=i=0∑2nWc(i)(Υk∣k−1(i)−V^k∣k−1)(Υk∣k−1(i)−V^k∣k−1)T+R
其中,R\boldsymbol{R}R是量测噪声协方差。
解读 这一步就是让侦察兵们**"变身成电压侦探"**,专门预测电池的端电压,再汇总出"电压预测报告"!
第一步:侦察兵变身电压侦探
上一轮得到的先验Sigma点 χk∣k−1(i)\chi_{k|k-1}^{(i)}χk∣k−1(i)(每个点都是一组完整的电池状态:SOC+两个极化电压),现在要钻进观测方程 这个"转换器"。
观测方程就是咱们之前说的电池"电压账本",它会根据每组状态算出一个对应的端电压------这些算出来的电压,就是预测观测点 Υk∣k−1(i)\Upsilon_{k|k-1}^{(i)}Υk∣k−1(i)。
简单说:每个电池状态侦察兵,都摇身一变成了一个"电压预测值"。
第二步:汇总出最靠谱的电压预测值
公式 V^k∣k−1=∑i=02nWm(i)Υk∣k−1(i)\hat{V}{k|k-1} = \sum{i=0}^{2n} W_m^{(i)} \Upsilon_{k|k-1}^{(i)}V^k∣k−1=∑i=02nWm(i)Υk∣k−1(i) 还是老规矩------给每个电压侦探的预测值发"信任分" Wm(i)W_m^{(i)}Wm(i)。
大本营侦察兵对应的电压预测值权重最高,边缘侦察兵的权重较低。把所有预测值乘以信任分再相加,就得到了最靠谱的预测端电压 V^k∣k−1\hat{V}_{k|k-1}V^k∣k−1 ------这就是算法"猜"出来的,电池此刻应该有的电压。
第三步:给电压预测画"误差圈圈"
光猜电压还不够,得知道这个猜测准不准!公式 Pzz=∑i=02nWc(i)(Υk∣k−1(i)−V^k∣k−1)(Υk∣k−1(i)−V^k∣k−1)T+R\boldsymbol{P}{zz} = \sum{i=0}^{2n} W_c^{(i)} \left( \Upsilon_{k|k-1}^{(i)} - \hat{V}{k|k-1} \right) \left( \Upsilon{k|k-1}^{(i)} - \hat{V}_{k|k-1} \right)^T + \boldsymbol{R}Pzz=∑i=02nWc(i)(Υk∣k−1(i)−V^k∣k−1)(Υk∣k−1(i)−V^k∣k−1)T+R 就是干这个的:
- (Υk∣k−1(i)−V^k∣k−1)\left( \Upsilon_{k|k-1}^{(i)} - \hat{V}_{k|k-1} \right)(Υk∣k−1(i)−V^k∣k−1):每个电压侦探的预测值,和综合预测值的差距------差距越大,说明电压预测的不确定性越高;
- Wc(i)W_c^{(i)}Wc(i):计算不确定性时的信任分,和之前状态预测的信任分逻辑一致;
- 最后加的 R\boldsymbol{R}R 是**"传感器误差红包"------代表电压传感器本身的测量误差(比如传感器老化、电磁干扰),就像侦探们手里的测量工具本身就有点不准。
算出来的 Pzz\boldsymbol{P}_{zz}Pzz 就是电压预测的误差范围**------数值越大,说明我们对这个预测电压越没把握。
- 步骤4:卡尔曼增益计算与状态修正
计算状态和观测的互协方差 Pxz\boldsymbol{P}{xz}Pxz,再计算卡尔曼增益 Kk\boldsymbol{K}kKk,最终得到后验状态估计(最优SOC) :
Pxz=∑i=02nWc(i)(χk∣k−1(i)−x^k∣k−1)(Υk∣k−1(i)−V^k∣k−1)T \boldsymbol{P}{xz} = \sum{i=0}^{2n} W_c^{(i)} \left( \chi_{k|k-1}^{(i)} - \hat{\boldsymbol{x}}{k|k-1} \right) \left( \Upsilon{k|k-1}^{(i)} - \hat{V}{k|k-1} \right)^T Pxz=i=0∑2nWc(i)(χk∣k−1(i)−x^k∣k−1)(Υk∣k−1(i)−V^k∣k−1)T
Kk=PxzPzz−1 \boldsymbol{K}k = \boldsymbol{P}{xz} \boldsymbol{P}{zz}^{-1} Kk=PxzPzz−1
x^k∣k=x^k∣k−1+Kk(Vk−V^k∣k−1) \hat{\boldsymbol{x}}{k|k} = \hat{\boldsymbol{x}}{k|k-1} + \boldsymbol{K}k \left( V_k - \hat{V}{k|k-1} \right) x^k∣k=x^k∣k−1+Kk(Vk−V^k∣k−1)
Pk∣k=Pk∣k−1−KkPzzKkT \boldsymbol{P}{k|k} = \boldsymbol{P}{k|k-1} - \boldsymbol{K}k \boldsymbol{P}{zz} \boldsymbol{K}k^T Pk∣k=Pk∣k−1−KkPzzKkT
其中,VkV_kVk是传感器实测端电压,Vk−V^k∣k−1V_k - \hat{V}{k|k-1}Vk−V^k∣k−1 被称为新息(衡量预测和测量的差距)。
解读 这一步就是UKF的**"终极决策环节"**------把"模型预测的状态"和"传感器测的电压"放在一起比对,最终算出最准的电池状态!咱们拆成4个小步骤,用"侦探破案"的思路讲明白:
第一步:算"状态和电压的关联度"------互协方差 Pxz\boldsymbol{P}_{xz}Pxz
公式里的两个差值,咱们早就认识了:
- χk∣k−1(i)−x^k∣k−1\chi_{k|k-1}^{(i)} - \hat{\boldsymbol{x}}_{k|k-1}χk∣k−1(i)−x^k∣k−1:状态侦察兵和平均预测状态的差距(状态偏差);
- Υk∣k−1(i)−V^k∣k−1\Upsilon_{k|k-1}^{(i)} - \hat{V}_{k|k-1}Υk∣k−1(i)−V^k∣k−1:电压侦探和平均预测电压的差距(电压偏差)。
这个公式的本质,就是给每一组偏差配对,再用权重加权求和 ------目的是搞清楚:"状态猜得不准"和"电压猜得不准"之间有没有关系?关系多大?
举个例子:如果状态偏差大的时候,电压偏差也跟着大,那Pxz\boldsymbol{P}_{xz}Pxz的数值就大,说明两者"强相关";反之则数值小,说明"没啥关系"。
简单说,Pxz\boldsymbol{P}_{xz}Pxz就是**"状态和电压的相关关系报告单"**。
第二步:算"信任分配系数"------卡尔曼增益 Kk\boldsymbol{K}_kKk
公式 Kk=PxzPzz−1\boldsymbol{K}k = \boldsymbol{P}{xz} \boldsymbol{P}_{zz}^{-1}Kk=PxzPzz−1 看起来复杂,其实就是**"权衡模型和传感器的信任度"**:
- 分母 Pzz\boldsymbol{P}{zz}Pzz 是电压预测的误差范围------Pzz\boldsymbol{P}{zz}Pzz 越小,说明电压预测越准,传感器越靠谱;
- 分子 Pxz\boldsymbol{P}_{xz}Pxz 是状态和电压的关联度------关联度越高,说明"电压不准"大概率是"状态没猜对"导致的。
所以 Kk\boldsymbol{K}_kKk 的物理意义超直白:当传感器测的电压和预测电压不一样时,我该用多大的力度去修正模型预测的状态?
- Kk\boldsymbol{K}_kKk 大 → 多信传感器,使劲修正状态;
- Kk\boldsymbol{K}_kKk 小 → 多信模型,稍微修正一下就行。
第三步:修正状态,得到最优解------后验估计 x^k∣k\hat{\boldsymbol{x}}_{k|k}x^k∣k
公式 x^k∣k=x^k∣k−1+Kk(Vk−V^k∣k−1)\hat{\boldsymbol{x}}{k|k} = \hat{\boldsymbol{x}}{k|k-1} + \boldsymbol{K}k \left( V_k - \hat{V}{k|k-1} \right)x^k∣k=x^k∣k−1+Kk(Vk−V^k∣k−1)
就是**"最终的修正公式",咱们把它翻译成大白话:
最优状态=模型预测状态+信任系数×电压差距 \text{最优状态} = \text{模型预测状态} + \text{信任系数} \times \text{电压差距} 最优状态=模型预测状态+信任系数×电压差距
这里的 (Vk−V^k∣k−1)\left( V_k - \hat{V}_{k|k-1} \right)(Vk−V^k∣k−1) 就是新息**------可以理解成"传感器给模型的纠错提示":
- 新息为正 → 实测电压比预测高,说明模型猜的SOC可能偏低,得往上调一点;
- 新息为负 → 实测电压比预测低,说明模型猜的SOC可能偏高,得往下调一点;
- 调多少?全看 Kk\boldsymbol{K}_kKk 这个"信任系数"说了算!
第四步:更新误差地图------后验协方差 Pk∣k\boldsymbol{P}_{k|k}Pk∣k
修正完状态还不算完,得更新一下"误差范围",为下一轮预测做准备。
公式 Pk∣k=Pk∣k−1−KkPzzKkT\boldsymbol{P}{k|k} = \boldsymbol{P}{k|k-1} - \boldsymbol{K}k \boldsymbol{P}{zz} \boldsymbol{K}_k^TPk∣k=Pk∣k−1−KkPzzKkT
的意思是:经过传感器的修正,我们对状态的猜测变得更靠谱了,误差范围缩小了!
缩小的幅度,正好是"信任系数×电压误差×信任系数"------修正越到位,误差缩小得越多。
最后得到的 Pk∣k\boldsymbol{P}_{k|k}Pk∣k 就是**"本轮最优状态的误差地图"**,下一轮的Sigma点,就要围绕这个新地图来撒啦!
--- 总结一下这一步的核心逻辑:用电压传感器的"实测值",去修正模型的"预测值",最终得到又准又靠谱的电池状态------这就是卡尔曼滤波"越猜越准"的秘诀!
UKF的两大"软肋"
尽管UKF是SOC估计的利器,但它有两个难以解决的问题:
- 参数敏感性 :过程噪声Q\boldsymbol{Q}Q和量测噪声R\boldsymbol{R}R需要人工设定,参数选得不好,估计结果会抖动或滞后;
- 鲁棒性不足:遇到极端工况(如急加速、低温)或模型失配时,UKF的估计误差会急剧增大,甚至发散。
这就是论文提出PO-RUKF的原因------给UKF装上"鲁棒防弹衣"和"自适应调参大脑"。
第三幕:PO-RUKF------让UKF变得更稳、更准
PO-RUKF的名字可以拆解为两部分:
- RUKF(Robust UKF) :引入H∞\text{H}_\inftyH∞鲁棒准则,解决鲁棒性问题;
- PO(Parrot Optimizer) :用鹦鹉优化算法,自适应优化过程噪声Q\boldsymbol{Q}Q。
Part 1: RUKF------引入H∞\text{H}_\inftyH∞准则,打造"抗干扰防弹衣"
RUKF的这个改进,就是给咱们的滤波算法穿上了一件"防坑防弹衣"!专治标准UKF"容易被传感器忽悠"的毛病,咱们用"买东西砍价"的故事讲明白:
先回顾标准UKF的"小缺点"
标准UKF算卡尔曼增益的公式是 Kk=PxzPzz−1\boldsymbol{K}k = \boldsymbol{P}{xz} \boldsymbol{P}{zz}^{-1}Kk=PxzPzz−1,这里的 Pzz\boldsymbol{P}{zz}Pzz 是电压预测的误差值。
- 如果 Pzz\boldsymbol{P}_{zz}Pzz 很小 → 算法觉得"我预测的电压超准",潜台词就是"传感器你可得靠谱点";
- 这时候 Pzz−1\boldsymbol{P}_{zz}^{-1}Pzz−1 会变得超大 → 卡尔曼增益 Kk\boldsymbol{K}_kKk 直接"爆表";
- 后果就是:算法无脑相信传感器------哪怕传感器突然抽风(比如电压突变、电磁干扰),算法也会跟着"跑偏",SOC估计值直接"飞上天"。
简单说,标准UKF就是个**"别人说啥都信的老实人"**,容易踩坑!
RUKF的"防坑秘诀":加个"安全垫"
RUKF的改进就俩公式,核心就一个字------"稳" !
Re,k=Pzz+γ−2 \boldsymbol{R}{e,k} = \boldsymbol{P}{zz} + \gamma^{-2} Re,k=Pzz+γ−2
Kk=PxzRe,k−1 \boldsymbol{K}k = \boldsymbol{P}{xz} \boldsymbol{R}_{e,k}^{-1} Kk=PxzRe,k−1
-
γ−2\gamma^{-2}γ−2 是啥?------ 算法的"冷静缓冲垫"
γ\gammaγ 是个调节参数(一般取1.0~1.5),γ−2\gamma^{-2}γ−2 就是一个固定的正数小尾巴 。不管 Pzz\boldsymbol{P}{zz}Pzz 变得多小,加上这个小尾巴后,Re,k\boldsymbol{R}{e,k}Re,k 都不会变成0!
这就像你砍价时,卖家说"这东西成本100,我只赚1块",你不会直接信------而是心里加个"缓冲":"他肯定赚了不止1块,我再砍20试试"。这个"缓冲"就是 γ−2\gamma^{-2}γ−2,让你不会被卖家的话牵着走。
-
γ\gammaγ 怎么调?------ 调节"谨慎程度"的开关
- γ\gammaγ 越小 → γ−2\gamma^{-2}γ−2 越大 → "缓冲垫"越厚 → 卡尔曼增益 Kk\boldsymbol{K}_kKk 越小 → 算法越保守,不信传感器的"鬼话",抗干扰能力拉满;
- γ\gammaγ 越大 → γ−2\gamma^{-2}γ−2 越小 → "缓冲垫"越薄 → 算法越大胆,越来越接近标准UKF;
- γ=1\gamma=1γ=1 → 是"保守"和"大胆"的平衡点,大部分场景都能用!
** 一句话总结**
RUKF不是"老实人",它是个**"有主见的聪明人"**------哪怕传感器说的天花乱坠,它也会留个心眼,不会轻易被带偏。这层"防弹衣",让它在复杂工况下也能稳稳输出精准的SOC估计!
Part 2: PO------让"数字鹦鹉"帮你调参,告别人工试错
过程噪声Q\boldsymbol{Q}Q决定了算法对"模型预测"的信任程度(Q\boldsymbol{Q}Q越大,越信任测量;Q\boldsymbol{Q}Q越小,越信任模型)。传统方法靠人工试错调Q\boldsymbol{Q}Q,而PO算法能自适应寻找最优Q\boldsymbol{Q}Q。
鹦鹉优化算法(PO)的核心思想
PO算法就是一群超聪明的**"数学鹦鹉"**,它们的目标只有一个------帮咱们找到最完美的Q\boldsymbol{Q}Q矩阵(过程噪声),让RUKF的SOC估计精准到离谱!
这群鹦鹉不靠公式硬算,而是靠四种"生存技能",在Q\boldsymbol{Q}Q的搜索空间里"找宝藏",咱们一个个拆开来唠:
-
觅食行为 ------ "跟着大佬有肉吃"
刚开始,鹦鹉们会在Q\boldsymbol{Q}Q的搜索范围里随机乱飞(相当于随机选几组Q\boldsymbol{Q}Q值)。
飞着飞着,它们发现:有的Q\boldsymbol{Q}Q值代入RUKF后,新息特别小(预测电压和实测电压几乎一致) ------这就是"好吃的食物"!
于是,所有鹦鹉都会朝着这个"最优食物位置"靠拢,优先探索大佬附近的区域。
这就像你找奶茶店,看到一条街的人都往一家店跑,你肯定也会跟着去凑热闹~
-
停留行为 ------ "蹲在宝地慢慢挖"
找到优质食物后,鹦鹉们不会立刻飞走,而是在附近"转圈溜达"------微调Q\boldsymbol{Q}Q的数值,比如把某个元素从1e−81e-81e−8改成1.2e−81.2e-81.2e−8、0.9e−80.9e-80.9e−8。
这个操作就是**"精细搜索"**,目的是避免错过"藏在角落的极品美食"。
好比你挖到一个金矿,不会只挖表面一层就走,而是会在周围深挖,争取把所有金子都带走!
-
沟通行为 ------ "鹦鹉之间不藏私"
这群鹦鹉超有爱,它们会实时分享自己找到的食物位置 。
如果一只鹦鹉在某个角落发现了更优的Q\boldsymbol{Q}Q值,它会立刻"喊"其他同伴过来;反之,如果一片区域啥都没有,大家也会互相提醒"别来这儿浪费时间"。
这就像同学之间分享考试重点,一人找到,全班受益,效率直接翻倍!
-
避险行为 ------ "遇到陷阱赶紧跑"
有时候,鹦鹉们会遇到**"假宝藏"------某个Q\boldsymbol{Q}Q值在小范围里看起来很好,但换个工况就不行了(这就是数学里的"局部最优解")。
这时候,鹦鹉们不会死磕,而是会 随机飞离这片区域**,去探索新的地方。好比你走进一条死胡同,不会硬着头皮撞墙,而是赶紧掉头,换条路继续找目的地~
一句话总结PO算法的这群鹦鹉,就是**"自带导航的调参小能手"**------它们靠"跟风、深挖、分享、避险"四大技能,帮咱们找到那个能让RUKF封神的最优Q\boldsymbol{Q}Q矩阵,彻底告别"人工试错调参"的痛苦!
PO与RUKF的融合流程(关键!)
PO是外层优化算法 ,不参与每一步滤波,而是周期性迭代优化,流程如下:
python
# 1. 初始化参数
Q_init = np.diag([1e-8, 1e-8, 1e-8]) # 初始Q矩阵
gamma = 1.2 # H∞鲁棒参数
window_size = 100 # 优化窗口(每100步优化一次)
PO_pop_size = 20 # 鹦鹉种群数量
PO_max_iter = 50 # PO最大迭代次数
# 2. 主循环:滤波+周期性优化
for k in range(total_steps):
# 2.1 执行RUKF滤波,得到SOC估计和新息序列
soc_est, innov = RUKF_step(x_prev, P_prev, I_k, V_k, Q_curr, gamma)
# 2.2 每积累window_size个新息,启动PO优化Q
if (k+1) % window_size == 0:
# 定义适应度函数:新息的均方误差(越小越好)
def fitness(Q):
_, innov_list = run_RUKF_window(Q, gamma, I_window, V_window)
return np.mean(np.square(innov_list))
# 启动PO算法优化Q
Q_opt = ParrotOptimizer(fitness, Q_search_range, PO_pop_size, PO_max_iter)
# 更新Q为最优值
Q_curr = Q_opt
# 2.3 更新状态,进入下一步
x_prev, P_prev = update_state(x_prev, soc_est, P_prev)
核心逻辑 :用新息的均方误差(MSE) 作为适应度函数------新息越小,说明模型预测和测量越吻合,对应的Q\boldsymbol{Q}Q就是最优的。PO算法通过迭代搜索,找到能最小化新息MSE的Q\boldsymbol{Q}Q,让RUKF始终处于最佳工作状态。
第四幕:效果如何?数据说话!
论文在FUDS联邦城市驾驶循环数据集上进行了实验(模拟真实城市路况,电流波动剧烈),对比了标准UKF、RUKF和PO-RUKF三种算法的性能,结果如下表所示:
| 算法 | 平均绝对误差 (AAE) | 均方根误差 (RMSE) | 稳定性(误差方差) |
|---|---|---|---|
| 标准UKF | 1.23% | 1.58% | 0.024 |
| RUKF | 0.87% | 1.12% | 0.012 |
| PO-RUKF (本文) | 0.52% | 0.68% | 0.005 |
实验结论:
- 精度翻倍:PO-RUKF的误差比标准UKF降低了超过一半,完全满足工业级SOC估计的精度要求(误差<1%);
- 稳定性更强:误差方差仅为标准UKF的1/5,即使在电流剧烈波动的工况下,SOC估计曲线也平滑无抖动;
- 自适应能力突出 :PO算法能根据工况变化动态调整Q\boldsymbol{Q}Q,无需人工干预,适合复杂的实际应用场景。
第五幕:核心公式与代码实现------从理论到实践
1. 锂电池二阶RC模型(状态方程+观测方程)
这是SOC估计的核心物理模型,包含SOC和两个极化电压的更新公式。
状态方程(SOC+极化电压更新)
{SOCk=SOCk−1−ηIkΔtCn×3600Vp1,k=Vp1,k−1e−Δtτ1+R1Ik(1−e−Δtτ1)Vp2,k=Vp2,k−1e−Δtτ2+R2Ik(1−e−Δtτ2) \begin{cases} \text{SOC}k = \text{SOC}{k-1} - \frac{\eta I_k \Delta t}{C_n \times 3600} \\ V_{p1,k} = V_{p1,k-1} e^{-\frac{\Delta t}{\tau_1}} + R_1 I_k \left(1 - e^{-\frac{\Delta t}{\tau_1}}\right) \\ V_{p2,k} = V_{p2,k-1} e^{-\frac{\Delta t}{\tau_2}} + R_2 I_k \left(1 - e^{-\frac{\Delta t}{\tau_2}}\right) \end{cases} ⎩ ⎨ ⎧SOCk=SOCk−1−Cn×3600ηIkΔtVp1,k=Vp1,k−1e−τ1Δt+R1Ik(1−e−τ1Δt)Vp2,k=Vp2,k−1e−τ2Δt+R2Ik(1−e−τ2Δt)
其中,τ1=R1C1\tau_1 = R_1 C_1τ1=R1C1,τ2=R2C2\tau_2 = R_2 C_2τ2=R2C2 是RC网络的时间常数。
解读
- SOC更新公式 :就像你钱包里的钱------SOCk−1SOC_{k-1}SOCk−1是你昨天剩的钱,IkΔtI_k ΔtIkΔt是今天花的钱(放电=花钱,充电=赚钱),ηηη是"手续费"(充电时电池不能100%存电,会耗一点),Cn×3600C_n×3600Cn×3600是你钱包的总容量。为啥乘3600?因为钱包容量按"小时"算(Ah),而花钱速度按"秒"算(A·s),得统一单位才能算清楚!
- 极化电压公式 :想象极化电压是个弹簧 ------电流IkI_kIk是拉弹簧的力。第一项Vp1,k−1e−Δt/τ1V_{p1,k-1}e^{-Δt/τ_1}Vp1,k−1e−Δt/τ1是弹簧的"回弹"(电流消失后,极化电压会慢慢衰减);第二项R1Ik(1−e−Δt/τ1)R_1I_k(1-e^{-Δt/τ_1})R1Ik(1−e−Δt/τ1)是弹簧的"拉伸"(电流越大、拉的时间越长,弹簧伸得越长)。τ1τ_1τ1就是弹簧的"弹性系数"------τ1τ_1τ1越大,弹簧回弹越慢,极化电压变化越平缓。
观测方程(端电压计算)
Vk=OCV(SOCk)−R0Ik−Vp1,k−Vp2,k V_k = \text{OCV}(\text{SOC}k) - R_0 I_k - V{p1,k} - V_{p2,k} Vk=OCV(SOCk)−R0Ik−Vp1,k−Vp2,k
其中,R0R_0R0是欧姆内阻,OCV(SOCk)\text{OCV}(\text{SOC}_k)OCV(SOCk)是SOC对应的开路电压。
趣味解读 这个公式就是电池的"电压账本"------OCV(SOCk)OCV(SOC_k)OCV(SOCk)是电池的"总家底"(没干活时的电压),然后要扣掉三笔钱:
- R0IkR_0 I_kR0Ik:电池内部的"过路费"------电流越大,过路费越高(就像堵车时油耗增加);
- Vp1,kV_{p1,k}Vp1,k和Vp2,kV_{p2,k}Vp2,k:电池的"疲劳费"------充放电太猛,电池内部反应跟不上,会临时消耗一点电压,等静置后又会恢复。
2. UKF的Sigma点生成公式
χk−1(i)={x^k−1∣k−1,i=0x^k−1∣k−1+(n+λ)Pk−1∣k−1i,i=1,2,...,nx^k−1∣k−1−(n+λ)Pk−1∣k−1i,i=n+1,...,2n \chi_{k-1}^{(i)} = \begin{cases} \hat{\boldsymbol{x}}{k-1|k-1}, & i=0 \\ \hat{\boldsymbol{x}}{k-1|k-1} + \sqrt{(n+\lambda) \boldsymbol{P}{k-1|k-1}}i, & i=1,2,...,n \\ \hat{\boldsymbol{x}}{k-1|k-1} - \sqrt{(n+\lambda) \boldsymbol{P}{k-1|k-1}}_i, & i=n+1,...,2n \end{cases} χk−1(i)=⎩ ⎨ ⎧x^k−1∣k−1,x^k−1∣k−1+(n+λ)Pk−1∣k−1 i,x^k−1∣k−1−(n+λ)Pk−1∣k−1 i,i=0i=1,2,...,ni=n+1,...,2n
解读 这就是UKF的"撒点魔法"!把它想象成给你的猜测画"误差圈":
- x^k−1∣k−1\hat{\boldsymbol{x}}_{k-1|k-1}x^k−1∣k−1:你当前最靠谱的猜测(比如猜SOC=0.8);
- Pk−1∣k−1\boldsymbol{P}_{k-1|k-1}Pk−1∣k−1:这个猜测的"靠谱程度"------P越大,误差圈越大,你越不确定;
- (n+λ)P\sqrt{(n+\lambda)\boldsymbol{P}}(n+λ)P :把误差圈"拉成"坐标轴方向(就像把椭圆变成长方形);
- 加和减这个值:在误差圈的每个方向上,前进一步、后退一步,再加上中心点,就得到了所有可能的"候选状态"。这些点就像你的"侦察兵",帮你试探所有可能的情况。
3. RUKF的鲁棒增益公式
Re,k=Pzz+γ−2 \boldsymbol{R}{e,k} = \boldsymbol{P}{zz} + \gamma^{-2} Re,k=Pzz+γ−2
Kk=PxzRe,k−1 \boldsymbol{K}k = \boldsymbol{P}{xz} \boldsymbol{R}_{e,k}^{-1} Kk=PxzRe,k−1
解读 这是给算法装的"安全气囊"!
- 标准UKF的增益是K=Pxz/Pzz\boldsymbol{K}=\boldsymbol{P}{xz}/\boldsymbol{P}{zz}K=Pxz/Pzz------如果Pzz\boldsymbol{P}_{zz}Pzz很小(算法觉得传感器超准),K会变得超大,算法会"无脑相信"传感器;
- γ−2\gamma^{-2}γ−2就是强行加的"安全垫" ------哪怕`Pzz\boldsymbol{P}_{zz}Pzz趋近于0,分母也不会变成0,K就不会失控。
- 你可以把γ\gammaγ当成"谨慎开关":γ\gammaγ越小→安全垫越厚→算法越谨慎,不信邪;γ\gammaγ越大→安全垫越薄→算法越"大胆",接近标准UKF。
4. PO算法的适应度函数
fitness(Q)=1N∑i=1Nεi2 \text{fitness}(\boldsymbol{Q}) = \frac{1}{N} \sum_{i=1}^N \varepsilon_i^2 fitness(Q)=N1i=1∑Nεi2
其中,εi=Vmeas,i−Vpred,i\varepsilon_i = V_{meas,i} - V_{pred,i}εi=Vmeas,i−Vpred,i 是新息(预测电压和实测电压的差值)。
解读 PO算法的"鹦鹉们"其实在玩打分游戏!
- Q\boldsymbol{Q}Q:鹦鹉们要猜的"答案"(过程噪声);
- εi\varepsilon_iεi:用这个Q跑算法,算出来的电压和真实电压的"差距"------差距越小,分数越高;
- 适应度函数:计算所有差距的"平均平方值"------这个值越小,说明Q越靠谱。鹦鹉们的目标就是找到能让这个分数最低的Q,就像找能让考试得分最高的复习方法!
代码实现(状态更新函数)
python
def state_update(soc_prev, v1_prev, v2_prev, I, dt, eta, Cn, R1, R2, C1, C2):
"""
锂电池二阶RC模型状态更新
:param soc_prev: 上一时刻SOC
:param v1_prev: 上一时刻极化电压Vp1
:param v2_prev: 上一时刻极化电压Vp2
:param I: 充放电电流(放电+,充电-)
:param dt: 时间步长(s)
:param eta: 库伦效率
:param Cn: 电池额定容量(Ah)
:param R1/R2/C1/C2: RC模型参数
:return: 下一时刻soc, v1, v2
"""
# SOC更新
soc = soc_prev - eta * I * dt / (Cn * 3600)
soc = np.clip(soc, 0.0, 1.0) # SOC限幅0~1
# 极化电压更新
tau1 = R1 * C1
v1 = v1_prev * np.exp(-dt / tau1) + R1 * I * (1 - np.exp(-dt / tau1))
tau2 = R2 * C2
v2 = v2_prev * np.exp(-dt / tau2) + R2 * I * (1 - np.exp(-dt / tau2))
return soc, v1, v2
5. PO-RUKF的核心代码片段(增益计算)
python
def rukf_update(x_pri, P_pri, sigma_x, V_meas, gamma, R):
"""
RUKF的观测更新环节,计算鲁棒卡尔曼增益并修正状态
:param x_pri: 先验状态估计
:param P_pri: 先验协方差
:param sigma_x: 先验Sigma点
:param V_meas: 实测端电压
:param gamma: H∞鲁棒参数
:param R: 量测噪声协方差
:return: 后验状态估计x_post,后验协方差P_post
"""
n = x_pri.shape[0]
lam = alpha**2 * (n + kappa) - n
Wm = np.zeros(2*n+1)
Wc = np.zeros(2*n+1)
Wm[0] = lam / (n + lam)
Wc[0] = lam / (n + lam) + (1 - alpha**2 + beta)
for i in range(1, 2*n+1):
Wm[i] = 1 / (2 * (n + lam))
Wc[i] = 1 / (2 * (n + lam))
# 计算预测观测点和先验观测
sigma_v = np.array([ocv(soc) - R0*I - v1 - v2 for soc, v1, v2 in sigma_x])
v_pri = np.sum(Wm * sigma_v)
# 计算Pzz, Pxz
Pzz = np.sum(Wc * (sigma_v - v_pri)[:, np.newaxis] * (sigma_v - v_pri)[:, np.newaxis].T) + R
Pxz = np.sum(Wc * (sigma_x - x_pri)[:, np.newaxis] * (sigma_v - v_pri)[:, np.newaxis].T)
# 鲁棒修正:计算Re
Re = Pzz + 1 / (gamma ** 2)
# 计算鲁棒卡尔曼增益
K = Pxz / Re
# 状态修正
x_post = x_pri + K * (V_meas - v_pri)
# 协方差修正
P_post = P_pri - K * Re * K.T
return x_post, P_post