@@ -411,86 +411,6 @@ class EIGEN_ALIGN_MAX Kalman3D_Wave {
411
411
// Symmetrize for numerical hygiene
412
412
Pext = T (0.5 ) * (Pext + Pext.transpose ());
413
413
}
414
-
415
- // Unified builder for (PCᵀ, S) = (P Cᵀ, C P Cᵀ + R)
416
- // Mirrors the summation order of the original measurement updates
417
- // to preserve float numerical precision.
418
- // - J_att: 3x3 (can be all-zero if not used).
419
- // - J_aw : nullptr if not used; 3x3 if used.
420
- // - include_ba: include accel bias block.
421
- // - direct_offset: >=0 for direct observed 3x1 block (e.g. OFF_S), else -1.
422
- // - Rm: measurement noise 3x3.
423
- EIGEN_STRONG_INLINE void build_PCt_and_S_sparse_ (
424
- const Matrix3& J_att,
425
- const Matrix3* J_aw,
426
- bool include_ba,
427
- int direct_offset,
428
- const Matrix3& Rm,
429
- Eigen::Matrix<T,NX,3 >& PCt,
430
- Matrix3& S_mat) const
431
- {
432
- PCt.setZero ();
433
- S_mat = Rm;
434
-
435
- constexpr int OFF_TH = 0 ;
436
-
437
- // --- θ block ---
438
- {
439
- bool use_theta = !J_att.isZero (1e-20 ); // small tolerance
440
- if (use_theta) {
441
- const auto P_all_th = Pext.template block <NX,3 >(0 , OFF_TH);
442
- const Matrix3 P_th_th = Pext.template block <3 ,3 >(OFF_TH, OFF_TH);
443
- PCt.noalias () += P_all_th * J_att.transpose ();
444
- S_mat.noalias () += J_att * P_th_th * J_att.transpose ();
445
- }
446
-
447
- // --- θ–a_w cross terms ---
448
- if (J_aw && use_theta) {
449
- const Matrix3 P_th_aw = Pext.template block <3 ,3 >(OFF_TH, OFF_AW);
450
- S_mat.noalias () += J_att * P_th_aw * J_aw->transpose ();
451
- S_mat.noalias () += (*J_aw) * P_th_aw.transpose () * J_att.transpose ();
452
- }
453
-
454
- // --- a_w block ---
455
- if (J_aw) {
456
- const auto P_all_aw = Pext.template block <NX,3 >(0 , OFF_AW);
457
- const Matrix3 P_aw_aw = Pext.template block <3 ,3 >(OFF_AW, OFF_AW);
458
- PCt.noalias () += P_all_aw * J_aw->transpose ();
459
- S_mat.noalias () += (*J_aw) * P_aw_aw * J_aw->transpose ();
460
- }
461
-
462
- // --- bias block ---
463
- if constexpr (with_accel_bias) {
464
- if (include_ba) {
465
- // θ–b_a cross
466
- if (use_theta) {
467
- const Matrix3 P_th_ba = Pext.template block <3 ,3 >(OFF_TH, OFF_BA);
468
- S_mat.noalias () += J_att * P_th_ba;
469
- S_mat.noalias () += P_th_ba.transpose () * J_att.transpose ();
470
- }
471
- // a_w–b_a cross
472
- if (J_aw) {
473
- const Matrix3 P_aw_ba = Pext.template block <3 ,3 >(OFF_AW, OFF_BA);
474
- S_mat.noalias () += (*J_aw) * P_aw_ba;
475
- S_mat.noalias () += P_aw_ba.transpose () * J_aw->transpose ();
476
- }
477
- // bias block itself
478
- const auto P_all_ba = Pext.template block <NX,3 >(0 , OFF_BA);
479
- const Matrix3 P_ba_ba = Pext.template block <3 ,3 >(OFF_BA, OFF_BA);
480
- PCt.noalias () += P_all_ba;
481
- S_mat.noalias () += P_ba_ba;
482
- }
483
- }
484
-
485
- // --- direct observed block (e.g. S pseudo-measurement) ---
486
- if (direct_offset >= 0 ) {
487
- const auto P_all_dir = Pext.template block <NX,3 >(0 , direct_offset);
488
- const Matrix3 P_dir_dir = Pext.template block <3 ,3 >(direct_offset, direct_offset);
489
- PCt.noalias () += P_all_dir;
490
- S_mat.noalias () += P_dir_dir;
491
- }
492
- }
493
- }
494
414
};
495
415
496
416
// Implementation
@@ -853,32 +773,78 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_acc_o
853
773
const T g_meas = acc_meas.norm ();
854
774
if (std::abs (g_meas - gravity_magnitude_) > T (2.0 ) * gravity_magnitude_) return ;
855
775
856
- // Predicted specific force: f_b = R_wb (a_w - g) + b_a(temp)
776
+ // Predicted specific force
857
777
const Vector3 v1hat = accelerometer_measurement_func (tempC);
858
778
859
779
// Jacobians wrt the only columns C touches
860
780
const Matrix3 J_att = -skew_symmetric_matrix (v1hat); // d f / d θ
861
781
const Matrix3 J_aw = R_wb (); // d f / d a_w
782
+ Matrix3 J_ba; // d f / d b_a
783
+ if constexpr (with_accel_bias) J_ba.setIdentity ();
862
784
863
785
const Vector3 r = acc_meas - v1hat;
864
786
865
- // Build PCᵀ and S (θ + a_w + b_a)
866
- Eigen::Matrix<T,NX,3 > PCt;
867
- Matrix3 S_mat;
868
- build_PCt_and_S_sparse_ (J_att, &J_aw, true , -1 , Racc, PCt, S_mat);
787
+ // Innovation covariance S = C P Cᵀ + Racc (3×3)
788
+ Matrix3 S_mat = Racc;
789
+ {
790
+ constexpr int OFF_TH = 0 ;
791
+ const int off_aw = OFF_AW;
792
+ const int off_ba = OFF_BA;
793
+
794
+ const Matrix3 P_th_th = Pext.template block <3 ,3 >(OFF_TH, OFF_TH);
795
+ const Matrix3 P_th_aw = Pext.template block <3 ,3 >(OFF_TH, off_aw);
796
+ const Matrix3 P_aw_aw = Pext.template block <3 ,3 >(off_aw, off_aw);
797
+
798
+ S_mat.noalias () += J_att * P_th_th * J_att.transpose ();
799
+ S_mat.noalias () += J_att * P_th_aw * J_aw.transpose ();
800
+ S_mat.noalias () += J_aw * P_th_aw.transpose () * J_att.transpose ();
801
+ S_mat.noalias () += J_aw * P_aw_aw * J_aw.transpose ();
802
+
803
+ if constexpr (with_accel_bias) {
804
+ const Matrix3 P_th_ba = Pext.template block <3 ,3 >(OFF_TH, off_ba);
805
+ const Matrix3 P_aw_ba = Pext.template block <3 ,3 >(off_aw, off_ba);
806
+ const Matrix3 P_ba_ba = Pext.template block <3 ,3 >(off_ba, off_ba);
869
807
870
- // Gain and update
808
+ S_mat.noalias () += J_att * P_th_ba * J_ba.transpose ();
809
+ S_mat.noalias () += J_aw * P_aw_ba * J_ba.transpose ();
810
+
811
+ S_mat.noalias () += J_ba * P_th_ba.transpose () * J_att.transpose ();
812
+ S_mat.noalias () += J_ba * P_aw_ba.transpose () * J_aw.transpose ();
813
+
814
+ S_mat.noalias () += J_ba * P_ba_ba * J_ba.transpose ();
815
+ }
816
+ }
817
+
818
+ // PCᵀ = P Cᵀ (NX×3)
819
+ Eigen::Matrix<T,NX,3 > PCt; PCt.setZero ();
820
+ {
821
+ constexpr int OFF_TH = 0 ;
822
+ const auto P_all_th = Pext.template block <NX,3 >(0 , OFF_TH);
823
+ const auto P_all_aw = Pext.template block <NX,3 >(0 , OFF_AW);
824
+ PCt.noalias () += P_all_th * J_att.transpose ();
825
+ PCt.noalias () += P_all_aw * J_aw.transpose ();
826
+
827
+ if constexpr (with_accel_bias) {
828
+ const auto P_all_ba = Pext.template block <NX,3 >(0 , OFF_BA);
829
+ PCt.noalias () += P_all_ba * J_ba.transpose (); // J_ba = I
830
+ }
831
+ }
832
+
833
+ // Gain
871
834
Eigen::LDLT<Matrix3> ldlt;
872
835
if (!safe_ldlt3_ (S_mat, ldlt, Racc.norm ())) return ;
873
836
const Eigen::Matrix<T,NX,3 > K = PCt * ldlt.solve (Matrix3::Identity ());
874
837
838
+ // State update
875
839
xext.noalias () += K * r;
876
840
877
- // Joseph covariance update
841
+ // Covariance update
878
842
joseph_update3_ (K, S_mat, PCt);
879
843
880
- // Apply quaternion correction and mirror base block
844
+ // Apply quaternion correction
881
845
applyQuaternionCorrectionFromErrorState ();
846
+
847
+ // Mirror base block
882
848
Pbase = Pext.topLeftCorner (BASE_N, BASE_N);
883
849
}
884
850
@@ -894,35 +860,36 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_mag_o
894
860
const T n_pred = v2hat.norm ();
895
861
if (n_meas < T (1e-6 ) || n_pred < T (1e-6 )) return ;
896
862
863
+ // Unit vectors for dot product check
897
864
Vector3 meas_n = mag_meas_body / n_meas;
898
865
Vector3 pred_n = v2hat / n_pred;
899
- const T dotp = meas_n.dot (pred_n);
866
+ T dotp = meas_n.dot (pred_n);
900
867
901
- const T DOT_DANGEROUS = T (0.2 ); // if |dot| < 0.2 (~78°) → yaw-only branch
868
+ const T DOT_DANGEROUS = T (0.2 ); // if |dot| < 0.2 (~> 78°) → yaw-only branch
902
869
const T YAW_CLAMP = T (0.105 ); // ~6° clamp per update
903
870
904
871
if (std::abs (dotp) >= DOT_DANGEROUS) {
905
- // -------- BRANCH 1: SAFE FULL 3D UPDATE --------
872
+ // BRANCH 1: SAFE FULL 3D UPDATE
906
873
const Vector3 meas_fixed = (dotp >= T (0 )) ? mag_meas_body : -mag_meas_body;
907
874
const Vector3 r = meas_fixed - v2hat;
908
875
909
876
const Matrix3 J_att = -skew_symmetric_matrix (v2hat);
910
877
911
- Eigen::Matrix<T,NX, 3 > PCt ;
912
- Matrix3 S_mat ;
913
- // θ only; no a_w, no b_a, no direct block
914
- build_PCt_and_S_sparse_ (J_att, /* J_aw */ nullptr , /* include_ba */ false ,
915
- /* direct_offset */ - 1 , Rmag, PCt, S_mat );
878
+ Matrix3 S_mat = Rmag ;
879
+ const Matrix3 P_th_th = Pext. template block < 3 , 3 >( 0 , 0 ) ;
880
+ S_mat. noalias () += J_att * P_th_th * J_att. transpose ();
881
+
882
+ Eigen::Matrix<T,NX, 3 > PCt = Pext. template block <NX, 3 >( 0 , 0 ) * J_att. transpose ( );
916
883
917
884
Eigen::LDLT<Matrix3> ldlt;
918
885
if (!safe_ldlt3_ (S_mat, ldlt, Rmag.norm ())) return ;
919
886
const Eigen::Matrix<T,NX,3 > K = PCt * ldlt.solve (Matrix3::Identity ());
920
887
921
888
xext.noalias () += K * r;
922
- joseph_update3_ (K, S_mat, PCt);
923
889
890
+ joseph_update3_ (K, S_mat, PCt);
924
891
} else {
925
- // -------- BRANCH 2: YAW-ONLY PROJECTED UPDATE --------
892
+ // BRANCH 2: YAW-ONLY PROJECTED UPDATE
926
893
const Vector3 gb = (R_wb () * Vector3 (0 ,0 ,1 )).normalized ();
927
894
const Matrix3 Hb = Matrix3::Identity () - gb * gb.transpose ();
928
895
@@ -937,16 +904,15 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_mag_o
937
904
938
905
const Vector3 r = Hb * (mag_meas_body - v2hat);
939
906
const Matrix3 J_att_proj = Hb * (-skew_symmetric_matrix (v2hat));
940
- const Matrix3 R_proj = Hb * Rmag * Hb.transpose ();
941
907
942
- Eigen::Matrix<T,NX, 3 > PCt ;
943
- Matrix3 S_mat ;
944
- // θ (projected); no a_w, no b_a, no direct block
945
- build_PCt_and_S_sparse_ (J_att_proj, /* J_aw */ nullptr , /* include_ba */ false ,
946
- /* direct_offset */ - 1 , R_proj, PCt, S_mat );
908
+ Matrix3 S_mat = Hb * Rmag * Hb. transpose () ;
909
+ const Matrix3 P_th_th = Pext. template block < 3 , 3 >( 0 , 0 ) ;
910
+ S_mat. noalias () += J_att_proj * P_th_th * J_att_proj. transpose ();
911
+
912
+ Eigen::Matrix<T,NX, 3 > PCt = Pext. template block <NX, 3 >( 0 , 0 ) * J_att_proj. transpose ( );
947
913
948
914
Eigen::LDLT<Matrix3> ldlt;
949
- if (!safe_ldlt3_ (S_mat, ldlt, R_proj .norm ())) return ;
915
+ if (!safe_ldlt3_ (S_mat, ldlt, S_mat .norm ())) return ;
950
916
const Eigen::Matrix<T,NX,3 > K = PCt * ldlt.solve (Matrix3::Identity ());
951
917
952
918
xext.noalias () += K * r;
@@ -960,8 +926,10 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_mag_o
960
926
joseph_update3_ (K, S_mat, PCt);
961
927
}
962
928
963
- // Apply quaternion correction & mirror base block
929
+ // Apply quaternion correction
964
930
applyQuaternionCorrectionFromErrorState ();
931
+
932
+ // Mirror base block
965
933
Pbase = Pext.topLeftCorner (BASE_N, BASE_N);
966
934
}
967
935
@@ -1022,11 +990,11 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::applyIntegralZeroPseudoM
1022
990
// Innovation: target S = 0
1023
991
const Vector3 r = -xext.template segment <3 >(off_S);
1024
992
1025
- // PCᵀ and S for direct-observed S block
1026
- Eigen::Matrix<T,NX, 3 > PCt ;
1027
- Matrix3 S_mat;
1028
- build_PCt_and_S_sparse_ ( Matrix3::Zero (), /* J_aw */ nullptr , /* include_ba */ false ,
1029
- /* direct_offset */ off_S, R_S, PCt, S_mat );
993
+ // Innovation covariance S = P_SS + R_S
994
+ Matrix3 S_mat = Pext. template block < 3 , 3 >(off_S, off_S) + R_S ;
995
+
996
+ // Cross covariance PCᵀ = P(:,S) (NX×3)
997
+ Eigen::Matrix<T,NX, 3 > PCt = Pext. template block <NX, 3 >( 0 , off_S );
1030
998
1031
999
Eigen::LDLT<Matrix3> ldlt;
1032
1000
if (!safe_ldlt3_ (S_mat, ldlt, R_S.norm ())) return ;
@@ -1038,7 +1006,7 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::applyIntegralZeroPseudoM
1038
1006
// Covariance update
1039
1007
joseph_update3_ (K, S_mat, PCt);
1040
1008
1041
- // Attitude may get nudged via cross-covariances
1009
+ // Apply quaternion correction (attitude may get nudged via cross-covariances)
1042
1010
applyQuaternionCorrectionFromErrorState ();
1043
1011
1044
1012
// Mirror base block
0 commit comments