09_kfupdate 逐行注释 Wiki (PSINS)¶
配套源码: kfupdate.m 所属层级: L3组合导航 前置依赖: 00_glvf / 01_earth / 03_insupdate / 08_kffk + 卡尔曼滤波五公式 学习目标: 读完能独立回答3个问题
🧩 函数作用一句话¶
标准离散KF:时间/量测/双更新三合一
📐 数学原理 / 物理意义¶
标准离散KF五公式(编号对照源码)¶
考虑线性离散随机系统:
x_k = Φ_{k,k-1}·x_{k-1} + w_{k-1} 状态方程
y_k = H_k·x_k + v_k 量测方程
E[w]=0, E[w·wᵀ]=Q; E[v]=0, E[v·vᵀ]=R; E[w·vᵀ]=0
卡尔曼滤波的标准五公式,对应本函数实现如下:
| 编号 | 公式名称 | 数学公式 | 源码位置 |
|---|---|---|---|
| ① | 状态预测(时间更新) | x̂ₖ⁻ = Φ·x̂ₖ₋₁⁺ | L37 或 L46 |
| ② | 协方差预测 | Pₖ⁻ = Φ·Pₖ₋₁⁺·Φᵀ + Γ·Q·Γᵀ | L38 或 L47 |
| ③ | 量测残差 | rₖ = yₖ − H·x̂ₖ⁻ | L55 |
| ④ | 卡尔曼增益 | Kₖ = Pₖ⁻·Hᵀ·(H·Pₖ⁻·Hᵀ + R)⁻¹ | L52-L53+L69 |
| ⑤a | 状态校正 | x̂ₖ⁺ = x̂ₖ⁻ + Kₖ·rₖ | L74 |
| ⑤b | 协方差校正 | Pₖ⁺ = Pₖ⁻ − Kₖ·(H·Pₖ⁻·Hᵀ + R)·Kₖᵀ | L75 |
注意:协方差校正 PSINS 用的是Joseph稳定形式的等价写法 P⁺ = P⁻ − K·S·Kᵀ(其中 S=HP⁻Hᵀ+R),而非常规 (I−KH)P⁻。前者数值更稳定,保证 P⁺ 半正定。
Sage-Husa 自适应 Rₖ 估计(L57-L67)的直觉¶
标准KF假设量测噪声R是固定的先验值,但实际中: - GPS信号遮挡时R会变大(多径效应) - 不同时段卫星几何分布变化,R应动态调整
Sage-Husa时变噪声统计估计器(PSINS只对R做自适应,Q固定):
直觉理解: - rₖ·rₖᵀ:实际残差的外积(真实量测波动大小的"观测") - H·Pₖ⁻·Hᵀ:KF模型预测的残差理论方差 - 两者之差 = "实际噪声比模型假设的大多少" - 若真实残差 r² ≫ HP⁻Hᵀ → 说明R设小了,应该调大 - 若真实残差 r² ≪ HP⁻Hᵀ → 说明R设大了,应该调小 - 指数平滑 b·旧R + (1−b)·新观测 → 避免跳变,形成滑动平均
再加上 [Rmin, Rmax]硬截断: - R < Rmin → 锁死在Rmin,防止过度自信导致KF"相信"错误量测 - R > Rmax → 锁死在Rmax,防止某次异常残差把R推到无穷大KF等效失效
最后 β衰减公式(L66):
这是变遗忘因子Sage-Husa:初期beta大→相信新残差,快速收敛;beta逐步减小→逐渐稳定到固定R。b参数控制衰减速度(b越小衰减越慢)。Pconstrain 防止P阵坍缩/爆炸的重要性¶
长期运行的KF常出现两种数值病态: 1. P阵坍缩(Pk(k,k)→0):KF过度自信,认为状态"完全知道",后续量测被K=PHᵀS⁻¹完全忽略(因为P→0→K→0),估计进入死锁再也不更新 2. P阵爆炸(Pk(k,k)→∞):模型Q或Φ有误差,P无界增长,增益K→∞导致状态"过度跟随"量测噪声,估计抖动剧烈
Pconstrain的处理策略不对称: - Pmin:硬截断 Pk(k,k)=Pmin,简单直接——不允许低于此下限 - Pmax:等比例缩放整行整列而非硬截断!因为直接截断对角线会破坏P的半正定性。设 ratio=√(Pmax/Pkk),对第k行和第k列同乘ratio,相当于对状态向量第k维做尺度变换后的相似变换,保持P对称半正定
📝 逐行注释¶
第一段:函数头与参数解析 (L1-L34)¶
| 行号 | 源码 | 注释 | 重点标记 |
|---|---|---|---|
| 1 | function kf = kfupdate(kf, yk, TimeMeasBoth) | 函数入口,输入输出都是kf结构体(in-place风格修改);yk量测向量;TimeMeasBoth模式开关 | |
| 2-11 | % Discrete-time Kalman filter... | 注释:原型说明,关键——TimeMeasBoth='T'(只传kf)时间更新;'M'仅量测更新;'B'(传kf+yk)两步都做 | |
| 12-23 | % Notes: (1) the Kalman filter stochastic models is... | 注释:①写出状态/量测方程+Q/R定义;②Sage-Husa自适应R公式 R=bR₋₁+(1−b)(rrᵀ−HP⁻Hᵀ) 写在注释里!配Rmin/Rmax约束;③fading遗忘因子;④Pmax/Pmin约束 | |
| 24-25 | % See also kfinit, kfinit0, kfupdatesq, kffk, kfhk, kfc2d, kffeedback, kfplot, RLS, ekf, ukf. | 参考函数族:初始化→状态转移→量测矩阵→离散化→反馈→绘图→RLS/EKF/UKF对比 | |
| 26-29 | % Copyright(c) 2009-2015... | 版权:严恭敏,西工大,2012/12/08初版,2013/08/29→2015/04/16→2017/06/01→2018/03/11多轮修订 | |
| 30-31 | if nargin==1, TimeMeasBoth = 'T'; end | ★参数默认值:只传1个参数(kf)→默认'T'纯时间更新。对应 test_SINS_GPS_153.m:28 的调用 kfupdate(kf) | ★ |
| 32-34 | elseif nargin==2, TimeMeasBoth = 'B'; end | 传2个参数(kf+yk)→默认'B'即 Time+Meas 都做。传3个参数就是显式指定了。 |
第二段①:时间更新 'T' 分支 (L36-L40)¶
| 行号 | 源码 | 注释 | 重点标记 |
|---|---|---|---|
| 35 | (空行) | ||
| 36 | if TimeMeasBoth=='T' % Time Updating | 分支①:仅时间更新(预测步)。高频SINS每步都走这个! | |
| 37 | kf.xkk_1 = kf.Phikk_1*kf.xk; | ★KF公式①:状态预测 x̂ₖ⁻ = Φ·x̂ₖ₋₁⁺。xkk_1 命名含义 = x(k | k-1) 即k时刻基于k-1时刻信息的预测 |
| 38 | kf.Pxkk_1 = kf.Phikk_1*kf.Pxk*kf.Phikk_1' + kf.Gammak*kf.Qk*kf.Gammak'; | ★KF公式②:协方差预测 Pₖ⁻ = ΦPΦᵀ + ΓQΓᵀ。Gammak是过程噪声驱动阵(把白噪声w从6维陀螺/加计噪声空间映射到15维状态空间) | ★★ |
| 39 | kf.xk = kf.xkk_1; kf.Pxk = kf.Pxkk_1; | 无测量时,预测值就是校正值:x̂ₖ⁺ ≡ x̂ₖ⁻,P̂ₖ⁺ ≡ P̂ₖ⁻。xk/Pxk始终存的是"当前最新校正后"的值 | |
| 40 | kf.measstop = kf.measstop - kf.nts; kf.measlost = kf.measlost + kf.nts; | ★工程化计时器:measstop>0表示"停用量测更新的剩余时间"(手动屏蔽量测),倒计时递减;measlost表示"距离上次成功量测已过多久"(丢包监控),累加计时 | ★ |
第二段②:量测更新的K增益+校正 'M'/'B'分支 (L42-L81)¶
| 行号 | 源码 | 注释 | 重点标记 |
|---|---|---|---|
| 41 | else | 进入非'T'分支:即'M'或'B' | |
| 42-44 | if TimeMeasBoth=='M'kf.xkk_1 = kf.xk;kf.Pxkk_1 = kf.Pxk; | 分支②:仅量测更新。假设时间更新在外部已做过,直接取xk/Pxk作为预测值(k | k-1 ≡ k |
| 45-48 | elseif TimeMeasBoth=='B'kf.xkk_1=Φ*xk; Pxkk_1=ΦPxkΦ'+ΓQΓ';measstop--; measlost++; | 分支③:先时间更新再量测更新,把L37-L40压缩在此。代码与L37-L40完全一致 | |
| 49-51 | else, error('TimeMeasBoth input error!'); | 参数合法性校验,非T/M/B直接报错终止 | |
| 52 | kf.Pxykk_1 = kf.Pxkk_1*kf.Hk'; | ★预计算互协方差 P_{xy,k | k-1} = Pₖ⁻·Hᵀ,后面K增益和S都要用,缓存省一次乘法 |
| 53 | kf.Py0 = kf.Hk*kf.Pxykk_1; | ★预计算量测预测协方差(不含R):Py0 = H·Pₖ⁻·Hᵀ。后面自适应R、残差χ²检验、S=Py0+R都要用 | ★ |
| 54 | kf.ykk_1 = kf.Hk*kf.xkk_1; | 量测预测 ŷ = H·x̂ₖ⁻,即"根据当前状态预测应该观测到什么" | |
| 55 | kf.rk = yk-kf.ykk_1; | ★★★KF公式③:残差 r = 真实观测 − 预测观测。符号绝对不能写反!写反就变预测-真实=-r,后面x校正变 x⁺=x⁻+K(-r) 方向全错 | ★★★ |
| 56 | idxbad = []; % bad measurement index | 初始化坏量测标记索引数组,后面自适应/野值检测用 | |
| 57-58 | if kf.adaptive==1for k=1:kf.m | ★★Sage-Husa自适应R开关。kf.m是量测维度。逐标量独立估计R的对角元(PSINS假设R为对角阵——各量测通道独立) | ★★ |
| 59 | if yk(k)>1e10, idxbad=[idxbad;k]; continue; end | 野值检测1:观测值>1e10判为无效(通常NaN/Inf或传感器掉线会返回超大值),标记idxbad并跳过R更新 | |
| 60 | ry = kf.rk(k)^2-kf.Py0(k,k); | ★★核心量:"实际噪声功率 - 模型预测残差功率"之差。ry>0→R应该调大;ry<0→R应该调小。ry = r² − HP⁻Hᵀ | ★★ |
| 61 | if ry<kf.Rmin(k,k), ry = kf.Rmin(k,k); end | 软下限:ry(即潜在新R候选)不能低于Rmin,低于则抬到Rmin(防止R→0导致K过大) | |
| 62-63 | if ry>kf.Rmax(k,k), kf.Rk(k,k)=Rmax;else Rk(k,k)=(1-beta)*Rk + beta*ry; | ★★Sage-Husa指数平滑公式: • ry超上限→硬截断Rmax • 否则 Rₖ = (1−β)·Rₖ₋₁ + β·ry β∈(0,1),通常β≈0.01~0.1,新息权重小、平滑更稳 | ★★★ |
| 64-65 | end % for循环结束end % adaptive结束 | 关闭k=1:m循环和adaptive==1分支 | |
| 66 | kf.beta = kf.beta/(kf.beta+kf.b); | ★★★变遗忘因子β衰减公式:β_new = β_old / (β_old + b)。数学上等价 β_t ≈ β₀/(1+t·b·β₀) 渐近递减。初期β大→快学新息;后期β→0→R固定不再更新,防止发散 | ★★★ |
| 67 | (空行) | ||
| 68 | kf.Pykk_1 = kf.Py0 + kf.Rk; | ★残差协方差(新息协方差)S = HP⁻Hᵀ + R。正定阵,后面求逆 | ★ |
| 69 | kf.Kk = kf.Pxykk_1*invbc(kf.Pykk_1); | ★★KF公式④:卡尔曼增益 K = Pₓᵧ · S⁻¹ = P⁻Hᵀ(HP⁻Hᵀ+R)⁻¹。invbc是PSINS自定义矩阵求逆函数(内部优先用mldivide,数值更稳) | ★★ |
| 70 | nomeas = union(find(kf.measstop>0),[kf.measmask;idxbad]); | ★★工程化量测屏蔽:nomeas = 禁止更新的量测通道索引。三类来源取并集: ① measstop>0:手动临时屏蔽通道 ② measmask:用户配置的永久屏蔽位 ③ idxbad:本次野值检测出的坏通道 2022/11/20修订版合并方式 | ★★ |
| 71-72 | hasmeas = (1:kf.m)';if ~isempty(nomeas), Kk(:,nomeas)=0; hasmeas(nomeas)=[]; end | ★禁用通道的K增益清零!K是(n×m)阵,对应禁用量测的整列设0→这些量测对状态校正毫无贡献。同时hasmeas保留"本次有效量测"列表 | ★ |
| 73 | if ~isempty(hasmeas), kf.measlog=bitor(...); kf.measlost(hasmeas)=0; end | ★两个工程计数器: ① measlog:位掩码日志,sum(2.^(ch-1))把哪些通道更新过编码成整数(事后回放用) ② measlost:成功更新的通道"丢包计时"清零,重新从0开始计 | ★ |
| 74 | kf.xk = kf.xkk_1 + kf.Kk*kf.rk; | ★★KF公式⑤a:状态校正 x̂ₖ⁺ = x̂ₖ⁻ + K·r。把量测残差按增益权重"回灌"到状态上 | ★★ |
| 75 | kf.Pxk = kf.Pxkk_1 - kf.Kk*kf.Pykk_1*kf.Kk'; | ★★KF公式⑤b:协方差校正 Joseph形式 P⁺ = P⁻ − K·S·Kᵀ。比(I−KH)P⁻多一次乘法但数值更稳定,天然保证P半正定 | ★★ |
| 76-78 | if length(kf.fading)>1s = diag(sqrt(kf.fading/2));kf.Pxk = s*(Pxk+Pxk')*s; | 分情况遗忘因子:若fading是向量(每个状态独立遗忘因子),则用对角阵s做缩放:P ← s·(P+Pᵀ)·s = ½·√α · (P+Pᵀ) · √α,等价P ← α·½·(P+Pᵀ) | |
| 79-81 | elsekf.Pxk = (kf.Pxk+kf.Pxk')*(kf.fading/2); | ★★★**单遗忘因子+对称化!**这一行做了三件事: ① (P+P')/2:强制对称化——浮点误差让P不对称累计几千步会非正定,每步对称化是保正定最便宜的手段② 乘 fading:遗忘因子α∈[1,1.02]。P←αP,人为"吹大"协方差,让KF对旧模型的信任度衰减,跟踪时变系统③ /2:因为P+P'已经双倍了,除2还原幅值 | ★★★ |
第二段③:状态约束+P约束收尾 (L82-L103)¶
| 行号 | 源码 | 注释 | 重点标记 |
|---|---|---|---|
| 82 | if kf.xconstrain==1 % 16/3/2018 | 状态硬约束开关(2018/3/16新增功能)。物理上不可能超范围的状态直接钳位 | |
| 83-89 | for k=1:kf.nif xk(k)<xmin(k), xk(k)=xmin(k);elseif xk(k)>xmax(k), xk(k)=xmax(k); endend | 逐维状态钳位:陀螺/加计零偏不可能超过±10°/h或±10mg,硬截断防止KF偶尔迭代出物理不可信值 | |
| 90 | (空行) | ||
| 91 | if kf.pconstrain==1 % 1/6/2017 | ★★★P阵协方差约束开关(2017/6/1新增,工程救星!) | ★★ |
| 92 | for k=1:kf.n | 逐对角线元素遍历(注意:只遍历对角,非对角通过行列缩放联动) | |
| 93-95 | if Pxk(k,k)<Pmin(k)Pxk(k,k)=Pmin(k);kf.Pmini(k)=kf.Pmini(k)+1; | ★Pmin处理:直接硬截断对角线元素。P(k,k)过低→KF过度自信→锁死。Pmini(k)计数器(2026/6/28新增):监控触发了多少次Pmin,事后分析KF健康度 | ★★ |
| 96 | elseif Pxk(k,k)>kf.Pmax(k) | ★Pmax处理:不能直接截断对角线!那样会破坏P的半正定性(对角截断后其他元没改,可能出现负特征值)。所以用"行列等比例缩放" | ★★★ |
| 97 | ratio = sqrt(kf.Pmax(k)/kf.Pxk(k,k)); | ★★★求缩放系数:目标 P(k,k) → Pmax,所以对角值要乘以 r² = Pmax/Pkk,即 r = √(Pmax/Pkk)。对整行×r、整列×r → 对角P(k,k)就变成了 r²·Pkk = Pmax | ★★★ |
| 98 | kf.Pxk(:,k) = Pxk(:,k)*ratio; Pxk(k,:) = Pxk(k,:)*ratio; | ★★整列×r + 整行×r = 对P做相似变换 D·P·Dᵀ,其中D=diag(1,..,r,..,1)。相似变换保持对称半正定性,P(k,k)刚好缩放到Pmax,其他元素P(i,k)与P(k,j)也被合理联动 | ★★★ |
| 99 | kf.Pmaxi(k)=kf.Pmaxi(k)+1; | Pmax触发计数器,同理Pmini,用于离线分析某状态P阵是否经常爆炸 | |
| 100-101 | end % elseif Pmax 结束end % for k=1:n 结束 | 关闭分支与循环 | |
| 102 | end % if pconstrain==1 结束 | 关闭P约束块 | |
| 103 | end % else (非T分支结束) | 最外层else关闭——量测更新整体结束。函数隐式return修改后的kf |
🔍 断点调试建议¶
目标脚本: test_SINS_GPS_153.m
调试1:观察第一次'T'更新后的P阵累积¶
- 在
kfupdate.mL39(kf.xk = kf.xkk_1;即'T'分支最后一行)设条件断点: 或者简单设普通断点,一直Continue直到第一次GNSS量测到达前(t≈1.0s前最后一步,t=0.98s或0.99s处) - 命中后观察:
调试2:观察第一次'M'更新——KF真正"起作用"的瞬间¶
- 在
kfupdate.mL75(kf.Pxk = ...协方差校正后)设断点,继续运行 -
第一次命中就是第一次GPS位置量测到达(mod(t,1)==0触发),立即观察:
% 量测更新前看一下(需要回到L55断或用L48之后的值) % 量测更新后(当前断点): diag_before = [某种方式看Pxkk_1对角] % 或者直接看L52断点 diag_after = diag(kf.Pxk); % 重点看位置块(7:9): diag_before(7:9) % 应该是累积到比较大的值,比如≈100² m²量级 diag_after(7:9) % ★骤减!应该≈Rk量级,比如(10m)²=100 m² % 这就是卡尔曼滤波的本质:量测到达瞬间,P被"压缩"到接近R,说明吸收了观测信息 % 再看增益K对应的位置列 kf.Kk(7:9, :) % 位置状态对量测的增益应该≈0.5~0.9之间,说明信息吸收充分 -
继续跑2~3次'M'更新,看:
- 每次更新后P位置对角↓,更新之间('T'更新)P位置对角↑
- 形成"锯齿状"——这就是典型的KF"预测膨胀+校正压缩"循环
- Sage-Husa:观察 kf.Rk 的对角元,如果残差持续大,Rk应该逐渐从初始值往上爬
❌ 初学者最容易踩的坑¶
坑①:忘记'T'/'M'/'B'三字含义,调用参数错配¶
常见错误1:以为 kfupdate(kf) 是"什么都不做初始化"
实际 kfupdate(kf) = 只做'T'时间更新!xk已经被Φ乘过了!
常见错误2:量测更新写成 kfupdate(kf, yk) 不写第三个参数
→ 实际默认'B' = 先Time再Meas
→ 如果之前循环里已经单独做过kfupdate(kf)(Time),
就会重复做一次Time,等效于"多走了一步"
→ Φ被多乘一次 → P比正常情况下大很多 → 估计发散
正确写法2选1,不要混用:
A) 高频: kf=kfupdate(kf); 低频: kf=kfupdate(kf,yk,'M'); (test_SINS_GPS_153写法)
B) 高频: kffk求Φ存好; 低频: kf=kfupdate(kf,yk,'B'); ("一步到位"写法)
坑②:残差 r = y−Hx⁻ 符号写反→估计直接反向飘¶
有人在自定义量测时手动套公式写成:
r = H*xkk_1 - yk; % ❌ 预测减实际=负残差
然后 xk = xkk_1 + K*r → 等效 xk = xkk_1 - K*(正确残差)
校正方向完全反了!状态误差越"校正"越大。
诊断方法:断L56,看rk符号——如果某通道真实误差应该是正的(位置偏东GPS显示偏东),
rk应该是正的但你看到是负的→99%是残差写反了
牢记:残差 = "实际看到什么" 减 "我们预测该看到什么"
即 r = 观测 减 预测观测 (y − Hx)
坑③:不做P阵对称化→数值累积→chol报错→KF崩¶
场景:有人把L78-80改成 kf.Pxk = kf.Pxkk_1 - kf.Kk*kf.Hk*kf.Pxkk_1;
(即所谓"标准形式" (I-KH)P⁻,并去掉了(P+P')/2)
跑了1000多步仿真一切正常,突然在第1835步报错:
Error using chol
Matrix must be positive definite.
原因:浮点加减不满足严格分配律
(I-KH)P⁻ = P⁻ - KHP⁻ 数学上= P⁻ - KSKᵀ,但数值上有1e-15级差异
每步积累一点点不对称(P-P'≈1e-14),累积几千步后P出现微小负特征值≈-1e-12
下一步求S=HP⁻Hᵀ+R,S也非正定,invbc/chol直接崩
PSINS作者的保险三件套(缺一不可):
① L75用Joseph形式 P ← P⁻ − K·S·Kᵀ (对称保持性最好)
② L80强制 (P+P')/2 每步对称化 (1e-15级误差被抹除)
③ L91-102 Pconstrain 防坍防炸 (数值病态兜底)
坑④:Pconstrain 开了但 Pmin/Pmax 设得差几个数量级¶
典型翻车:Pmin=[1e-20, ...] 设太小
→ P坍缩到1e-18时还没触发Pmin
→ K=P⁻HᵀS⁻¹ → K→0 → 状态死锁,再准的量测也"吸收不动"
反向翻车:Pmax=[1e20, ...] 设太大
→ P爆炸到1e15还没触发Pmax
→ K→∞ → x⁺=x⁻+K·r 对量测噪声过敏,估计抖成筛子
经验值(15状态SINS/GPS):
Pmin(φ) ≈ (0.001°)² = (1e-5 rad)² ≈ 1e-10
Pmin(dv)≈ (0.001m/s)² = 1e-6
Pmin(dp)≈ (0.1m)² = 1e-2
Pmax ≈ 1e6 × Pmin 比较稳妥
🎯 配套练习¶
练习题目1:关闭Pconstrain看P阵坍缩死锁¶
修改 /workspace/psins/demos/test_SINS_GPS_153.m,找到kfinit后设置pconstrain的位置(或在kfinit行之后加一行):
% 在 test_SINS_GPS_153.m 中 kfinit 之后添加/修改:
kf.pconstrain = 0; % 关闭P约束(默认应该是1,注释掉就是0或手动写0)
kf.adaptive = 0; % 顺便关掉自适应R,观察更纯粹的坍缩
% 同时修改仿真时间拉长点:
% 找到NN/totalTime参数,改成1000秒(原默认可能是短时间)
然后运行仿真,结束后画P阵对角线:
% 在仿真结束后的kfplot调用之前,手动画:
figure;
subplot(3,1,1);
plot(kf.Pxk_log(:,1:3)); title('姿态误差P对角 (rad^2)'); grid on;
subplot(3,1,2);
plot(kf.Pxk_log(:,4:6)); title('速度误差P对角 (m^2/s^2)'); grid on;
subplot(3,1,3);
plot(kf.Pxk_log(:,7:9)); title('位置误差P对角 (m^2)'); grid on;
xlabel('仿真步数');
% 观察现象:
% 某些对角元素会单调下降到 1e-25 ~ 1e-30 之间的极小值并卡住
% 对应状态的 K(k,:) 增益也变成≈0——KF对该状态已经"死锁",不再相信任何量测
进阶思考题:把pconstrain重新开回=1,画Pmini/Pmaxi计数器 vs 时间:
% 假设你把kf.Pmini/kf.Pmaxi也记录到log里了
figure; bar([kf.Pmini; kf.Pmaxi]');
legend('Pmin触发次数','Pmax触发次数');
xlabel('状态编号(1~15)');
% 看看哪个状态最容易坍缩(一般是零偏10~15维,因为Q最小)、哪个最容易炸
练习题目2:Sage-Husa β衰减可视化¶
写一个小脚本纯数学验证β衰减公式:
%% 参数
b = 0.001; % PSINS默认b,查kfinit.m(通常很小)
beta_init = 0.99;% 初始beta
N = 2000; % 迭代步数(模拟2000次量测更新≈2000s仿真)
%% 迭代
beta_hist = zeros(N,1);
beta = beta_init;
for k = 1:N
beta_hist(k) = beta;
beta = beta / (beta + b); % 对应 L66
end
%% 绘图
figure;
semilogy(1:N, beta_hist, 'b-', 'LineWidth', 1.5);
xlabel('量测更新步数 k'); ylabel('beta'); grid on;
title('Sage-Husa变遗忘因子beta衰减曲线');
%% 验证渐近公式: beta ≈ beta0 / (1 + k*b*beta0) 近似解
hold on;
k_vec = 1:N;
beta_approx = beta_init ./ (1 + k_vec * b * beta_init);
semilogy(k_vec, beta_approx, 'r--', 'LineWidth', 1.5);
legend('精确迭代值', '近似公式 beta0/(1+k*b*beta0)');
% 结论:b越小,beta衰减越慢,自适应R的"学习时间"越长但越稳
% b越大,beta衰减越快,初期R剧烈变化,后期迅速冻结
%% 再看 R 的实际变化:假设ry恒定=1,R从10开始
b = 0.001; beta = 0.99;
R_hist = zeros(N,1); R = 10; ry = 1; % 真实R应该=1,初始R误设为10(太保守)
for k = 1:N
R_hist(k) = R;
R = (1-beta)*R + beta*ry;
beta = beta / (beta + b);
end
figure; plot(1:N, R_hist, 'LineWidth',1.5); grid on;
xlabel('步数'); ylabel('自适应R估计值'); title('Sage-Husa从R=10收敛到真R=1的过程');
% 观察:初期因beta大→R快速下降;后期beta→0→R停在接近1处,不再变