@@ -378,45 +378,14 @@ class EIGEN_ALIGN_MAX Kalman3D_Wave {
378
378
// Quaternion & small-angle helpers (kept)
379
379
void applyQuaternionCorrectionFromErrorState (); // apply correction to qref using xext(0..2)
380
380
void normalizeQuat ();
381
-
381
+ void vanLoanDiscretization_12x3 (const Eigen::Matrix<T,12 ,12 >& A,
382
+ const Eigen::Matrix<T,12 ,3 >& G,
383
+ const Eigen::Matrix<T,3 ,3 >& Sigma_c,
384
+ T Ts,
385
+ Eigen::Matrix<T,12 ,12 >& Phi,
386
+ Eigen::Matrix<T,12 ,12 >& Qd) const ;
382
387
static void PhiAxis4x1_analytic (T tau, T h, Eigen::Matrix<T,4 ,4 >& Phi_axis);
383
388
static void QdAxis4x1_analytic (T tau, T h, T sigma2, Eigen::Matrix<T,4 ,4 >& Qd_axis);
384
-
385
- // Propagate top-left attitude (+ optional gyro bias) covariance block
386
- // This covers the 3x3 (attitude-only) or 6x6 (attitude+gyro bias) base state.
387
- // Equivalent to P ← Φ P Φᵀ + Q
388
- template <typename PBlockType>
389
- void propagate_att_bias_cov (PBlockType Pblock,
390
- const Eigen::Matrix<T,BASE_N,BASE_N>& Phi_block,
391
- const Eigen::Matrix<T,BASE_N,BASE_N>& Q_block)
392
- {
393
- // Propagate covariance
394
- Pblock = Phi_block * Pblock * Phi_block.transpose ();
395
-
396
- // Add process noise
397
- Pblock += Q_block;
398
-
399
- // Enforce symmetry
400
- Pblock = T (0.5 ) * (Pblock + Pblock.transpose ());
401
- }
402
-
403
- // Propagate one axis [v, p, S, a] 4x4 covariance
404
- // Each axis is independent, so we update three 4x4 diagonal blocks separately.
405
- // Equivalent to P ← Φ P Φᵀ + Q , with symmetry enforced.
406
- template <typename PBlockType>
407
- void propagate_axis_cov (PBlockType Pblock,
408
- const Eigen::Matrix<T,4 ,4 >& Phi_axis,
409
- const Eigen::Matrix<T,4 ,4 >& Qd_axis)
410
- {
411
- // Propagate covariance
412
- Pblock = Phi_axis * Pblock * Phi_axis.transpose ();
413
-
414
- // Add process noise
415
- Pblock += Qd_axis;
416
-
417
- // Enforce symmetry
418
- Pblock = T (0.5 ) * (Pblock + Pblock.transpose ());
419
- }
420
389
};
421
390
422
391
// Implementation
@@ -576,10 +545,9 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::initialize_from_acc(Vect
576
545
}
577
546
578
547
template <typename T, bool with_gyro_bias, bool with_accel_bias>
579
- void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::time_update(
580
- Vector3 const & gyr, T Ts)
548
+ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::time_update(Vector3 const & gyr, T Ts)
581
549
{
582
- // ===== Mean propagation =====
550
+ // Attitude mean propagation
583
551
Vector3 gyro_bias;
584
552
if constexpr (with_gyro_bias) {
585
553
gyro_bias = xext.template segment <3 >(3 );
@@ -590,15 +558,16 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::time_update(
590
558
591
559
// Δθ = ω Ts → quaternion increment
592
560
Eigen::Quaternion<T> dq = quat_from_delta_theta ((last_gyr_bias_corrected * Ts).eval ());
561
+
562
+ // Propagate: right-multiply (matches correction side and F/Jacobians signs )
593
563
qref = qref * dq;
594
564
qref.normalize ();
595
565
596
- // Build F, Q
597
- MatrixNX F_a_ext;
598
- MatrixNX Q_a_ext;
566
+ // Build exact discrete transition & process Q
567
+ MatrixNX F_a_ext; MatrixNX Q_a_ext;
599
568
assembleExtendedFandQ (Vector3::Zero (), Ts, F_a_ext, Q_a_ext);
600
569
601
- // Linear state mean [v,p,S,a_w]
570
+ // Mean propagation for linear subsystem [v,p,S,a_w]
602
571
Eigen::Matrix<T,12 ,1 > x_lin_prev;
603
572
x_lin_prev.template segment <3 >(0 ) = xext.template segment <3 >(OFF_V);
604
573
x_lin_prev.template segment <3 >(3 ) = xext.template segment <3 >(OFF_P);
@@ -608,80 +577,82 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::time_update(
608
577
const auto Phi_lin = F_a_ext.template block <12 ,12 >(OFF_V, OFF_V);
609
578
Eigen::Matrix<T,12 ,1 > x_lin_next = Phi_lin * x_lin_prev;
610
579
580
+ // write back mean
611
581
xext.template segment <3 >(OFF_V) = x_lin_next.template segment <3 >(0 );
612
582
xext.template segment <3 >(OFF_P) = x_lin_next.template segment <3 >(3 );
613
583
xext.template segment <3 >(OFF_S) = x_lin_next.template segment <3 >(6 );
614
584
xext.template segment <3 >(OFF_AW) = x_lin_next.template segment <3 >(9 );
615
585
616
- // ===== Covariance propagation (block-wise exact) =====
617
- constexpr int NA = BASE_N; // attitude (+ gyro bias)
618
- constexpr int NL = 12 ; // [v p S a_w]
619
- constexpr int NC = with_accel_bias ? 3 : 0 ;
620
-
621
- // F and Q blocks
622
- const Eigen::Matrix<T,NA,NA> F_AA = F_a_ext.template block <NA,NA>(0 ,0 );
623
- const Eigen::Matrix<T,NL,NL> F_LL = F_a_ext.template block <NL,NL>(OFF_V, OFF_V);
624
- Eigen::Matrix<T,NC,NC> F_CC;
625
- if constexpr (with_accel_bias) F_CC.setIdentity ();
626
-
627
- const Eigen::Matrix<T,NA,NA> Q_AA = Q_a_ext.template block <NA,NA>(0 ,0 );
628
- const Eigen::Matrix<T,NL,NL> Q_LL = Q_a_ext.template block <NL,NL>(OFF_V, OFF_V);
629
- Eigen::Matrix<T,NC,NC> Q_CC;
630
- if constexpr (with_accel_bias) Q_CC = Q_a_ext.template block <NC,NC>(OFF_BA, OFF_BA);
631
-
632
- // Old P blocks
633
- const Eigen::Matrix<T,NA,NA> P_AA = Pext.template block <NA,NA>(0 ,0 );
634
- const Eigen::Matrix<T,NL,NL> P_LL = Pext.template block <NL,NL>(OFF_V, OFF_V);
635
- Eigen::Matrix<T,NA,NL> P_AL = Pext.template block <NA,NL>(0 , OFF_V);
636
-
637
- Eigen::Matrix<T,NC,NC> P_CC;
638
- Eigen::Matrix<T,NA,NC> P_AC;
639
- Eigen::Matrix<T,NL,NC> P_LC;
640
- if constexpr (with_accel_bias) {
641
- P_CC = Pext.template block <NC,NC>(OFF_BA, OFF_BA);
642
- P_AC = Pext.template block <NA,NC>(0 , OFF_BA);
643
- P_LC = Pext.template block <NL,NC>(OFF_V, OFF_BA);
644
- }
645
-
646
- // Diagonal block updates
647
- Eigen::Matrix<T,NA,NA> P_AA_new = F_AA * P_AA * F_AA.transpose () + Q_AA;
648
- Eigen::Matrix<T,NL,NL> P_LL_new = F_LL * P_LL * F_LL.transpose () + Q_LL;
586
+ // Covariance propagation (optimized for current F/Q structure)
587
+ {
588
+ constexpr int NA = BASE_N;
589
+ constexpr int NL = 12 ;
590
+
591
+ const auto F_AA = F_a_ext.template block <NA,NA>(0 ,0 );
592
+ const auto F_LL = F_a_ext.template block <NL,NL>(OFF_V,OFF_V);
593
+
594
+ Eigen::Matrix<T,NA,NA> P_AA = Pext.template block <NA,NA>(0 ,0 );
595
+ Eigen::Matrix<T,NL,NL> P_LL = Pext.template block <NL,NL>(OFF_V,OFF_V);
596
+ Eigen::Matrix<T,NA,NL> P_AL = Pext.template block <NA,NL>(0 ,OFF_V);
597
+
598
+ Eigen::Matrix<T,NA,NA> P_AA_new;
599
+ {
600
+ Eigen::Matrix<T,NA,NA> tmp;
601
+ tmp.noalias () = F_AA * P_AA;
602
+ P_AA_new.noalias () = tmp * F_AA.transpose ();
603
+ P_AA_new += Q_a_ext.template block <NA,NA>(0 ,0 );
604
+ }
649
605
650
- Eigen::Matrix<T,NC,NC> P_CC_new;
651
- if constexpr (with_accel_bias) {
652
- P_CC_new = P_CC + Q_CC; // F_CC = I
653
- }
606
+ Eigen::Matrix<T,NL,NL> P_LL_new;
607
+ {
608
+ Eigen::Matrix<T,NL,NL> tmp;
609
+ tmp.noalias () = F_LL * P_LL;
610
+ P_LL_new.noalias () = tmp * F_LL.transpose ();
611
+ P_LL_new += Q_a_ext.template block <NL,NL>(OFF_V,OFF_V);
612
+ }
654
613
655
- // Cross blocks
656
- Eigen::Matrix<T,NA,NL> P_AL_new = F_AA * P_AL * F_LL.transpose ();
657
- Eigen::Matrix<T,NA,NC> P_AC_new;
658
- Eigen::Matrix<T,NL,NC> P_LC_new;
659
- if constexpr (with_accel_bias) {
660
- P_AC_new = F_AA * P_AC;
661
- P_LC_new = F_LL * P_LC;
662
- }
614
+ Eigen::Matrix<T,NA,NL> P_AL_new;
615
+ {
616
+ Eigen::Matrix<T,NA,NL> tmp;
617
+ tmp.noalias () = F_AA * P_AL;
618
+ P_AL_new.noalias () = tmp * F_LL.transpose ();
619
+ }
663
620
664
- // Write back
665
- Pext.template block <NA,NA>(0 ,0 ) = P_AA_new;
666
- Pext.template block <NL,NL>(OFF_V,OFF_V) = P_LL_new;
667
- Pext.template block <NA,NL>(0 ,OFF_V) = P_AL_new;
668
- Pext.template block <NL,NA>(OFF_V,0 ) = P_AL_new.transpose ();
621
+ if constexpr (with_accel_bias) {
622
+ constexpr int NB = 3 ;
623
+ Eigen::Matrix<T,NB,NB> P_BB = Pext.template block <NB,NB>(OFF_BA,OFF_BA);
624
+ Eigen::Matrix<T,NA,NB> P_AB = Pext.template block <NA,NB>(0 ,OFF_BA);
625
+ Eigen::Matrix<T,NL,NB> P_LB = Pext.template block <NL,NB>(OFF_V,OFF_BA);
626
+
627
+ Eigen::Matrix<T,NA,NB> P_AB_new = F_AA * P_AB;
628
+ Eigen::Matrix<T,NL,NB> P_LB_new = F_LL * P_LB;
629
+ Eigen::Matrix<T,NB,NB> P_BB_new = P_BB + Q_a_ext.template block <NB,NB>(OFF_BA,OFF_BA);
630
+
631
+ Pext.template block <NA,NA>(0 ,0 ) = P_AA_new;
632
+ Pext.template block <NL,NL>(OFF_V,OFF_V) = P_LL_new;
633
+ Pext.template block <NA,NL>(0 ,OFF_V) = P_AL_new;
634
+ Pext.template block <NL,NA>(OFF_V,0 ) = P_AL_new.transpose ();
635
+
636
+ Pext.template block <NA,NB>(0 ,OFF_BA) = P_AB_new;
637
+ Pext.template block <NB,NA>(OFF_BA,0 ) = P_AB_new.transpose ();
638
+ Pext.template block <NL,NB>(OFF_V,OFF_BA) = P_LB_new;
639
+ Pext.template block <NB,NL>(OFF_BA,OFF_V) = P_LB_new.transpose ();
640
+ Pext.template block <NB,NB>(OFF_BA,OFF_BA) = P_BB_new;
641
+ } else {
642
+ Pext.template block <NA,NA>(0 ,0 ) = P_AA_new;
643
+ Pext.template block <NL,NL>(OFF_V,OFF_V) = P_LL_new;
644
+ Pext.template block <NA,NL>(0 ,OFF_V) = P_AL_new;
645
+ Pext.template block <NL,NA>(OFF_V,0 ) = P_AL_new.transpose ();
646
+ }
669
647
670
- if constexpr (with_accel_bias) {
671
- Pext.template block <NC,NC>(OFF_BA,OFF_BA) = P_CC_new;
672
- Pext.template block <NA,NC>(0 ,OFF_BA) = P_AC_new;
673
- Pext.template block <NC,NA>(OFF_BA,0 ) = P_AC_new.transpose ();
674
- Pext.template block <NL,NC>(OFF_V,OFF_BA) = P_LC_new;
675
- Pext.template block <NC,NL>(OFF_BA,OFF_V) = P_LC_new.transpose ();
648
+ // Optional symmetry enforcement (remove once validated):
649
+ Pext = T (0.5 ) * (Pext + Pext.transpose ());
676
650
}
677
651
678
- // Enforce symmetry
679
- Pext = T (0.5 ) * (Pext + Pext.transpose ());
680
-
681
- // Mirror base block
682
- Pbase = Pext.topLeftCorner (NA, NA);
652
+ // Mirror base covariance
653
+ Pbase = Pext.topLeftCorner (BASE_N, BASE_N);
683
654
684
- // ===== periodic pseudo-measurement =====
655
+ // Drift correction on S
685
656
if (++pseudo_update_counter_ >= PSEUDO_UPDATE_PERIOD) {
686
657
applyIntegralZeroPseudoMeas ();
687
658
pseudo_update_counter_ = 0 ;
@@ -729,93 +700,46 @@ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_parti
729
700
}
730
701
731
702
template <typename T, bool with_gyro_bias, bool with_accel_bias>
732
- void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_acc_only(
733
- Vector3 const & acc_meas, T tempC)
734
- {
735
- // Gate accel magnitude
736
- const T g_meas = acc_meas.norm ();
737
- if (std::abs (g_meas - gravity_magnitude_) > T (2.0 ) * gravity_magnitude_) return ;
738
-
739
- // Predicted specific force
740
- const Vector3 v1hat = accelerometer_measurement_func (tempC);
741
-
742
- // Jacobians wrt the only columns C touches
743
- const Matrix3 J_att = -skew_symmetric_matrix (v1hat); // d f / d θ
744
- const Matrix3 J_aw = R_wb (); // d f / d a_w
745
- Matrix3 J_ba; // d f / d b_a
746
- if constexpr (with_accel_bias) J_ba.setIdentity ();
747
-
748
- const Vector3 r = acc_meas - v1hat;
749
-
750
- // Column offsets
751
- constexpr int OFF_TH = 0 ; // attitude error starts at 0
752
- const int off_aw = OFF_AW;
753
- const int off_ba = OFF_BA;
703
+ void Kalman3D_Wave<T, with_gyro_bias, with_accel_bias>::measurement_update_acc_only(Vector3 const & acc_meas, T tempC) {
754
704
755
- // ---------- S = C P Cᵀ + Racc (3×3) ----------
756
- Matrix3 S_mat = Racc;
757
-
758
- const Matrix3 P_th_th = Pext.template block <3 ,3 >(OFF_TH, OFF_TH);
759
- const Matrix3 P_th_aw = Pext.template block <3 ,3 >(OFF_TH, off_aw);
760
- const Matrix3 P_aw_aw = Pext.template block <3 ,3 >(off_aw, off_aw);
705
+ // Accel magnitude sanity check
706
+ T g_meas = acc_meas.norm ();
707
+ if (std::abs (g_meas - gravity_magnitude_) > T (2.0 * gravity_magnitude_)) {
708
+ return ; // reject this update
709
+ }
761
710
762
- S_mat.noalias () += J_att * P_th_th * J_att.transpose ();
763
- S_mat.noalias () += J_att * P_th_aw * J_aw.transpose ();
764
- S_mat.noalias () += J_aw * P_th_aw.transpose () * J_att.transpose ();
765
- S_mat.noalias () += J_aw * P_aw_aw * J_aw.transpose ();
711
+ const Vector3 v1hat = accelerometer_measurement_func (tempC);
766
712
713
+ // Cext: (3 x NX)
714
+ Matrix<T, 3 , NX> Cext = Matrix<T, 3 , NX>::Zero ();
715
+ // d f_b / d (attitude error)
716
+ Cext.template block <3 ,3 >(0 ,0 ) = -skew_symmetric_matrix (v1hat);
717
+ // d f_b / d a_w
718
+ Cext.template block <3 ,3 >(0 , OFF_AW) = R_wb ();
767
719
if constexpr (with_accel_bias) {
768
- const Matrix3 P_th_ba = Pext.template block <3 ,3 >(OFF_TH, off_ba);
769
- const Matrix3 P_aw_ba = Pext.template block <3 ,3 >(off_aw, off_ba);
770
- const Matrix3 P_ba_ba = Pext.template block <3 ,3 >(off_ba, off_ba);
771
-
772
- S_mat.noalias () += J_att * P_th_ba * J_ba.transpose ();
773
- S_mat.noalias () += J_aw * P_aw_ba * J_ba.transpose ();
774
-
775
- S_mat.noalias () += J_ba * P_th_ba.transpose () * J_att.transpose ();
776
- S_mat.noalias () += J_ba * P_aw_ba.transpose () * J_aw.transpose ();
777
-
778
- S_mat.noalias () += J_ba * P_ba_ba * J_ba.transpose ();
720
+ Cext.template block <3 ,3 >(0 ,OFF_BA) = Matrix3::Identity (); // d f_b / d b_acc
779
721
}
780
722
781
- // ---------- PCᵀ = P Cᵀ (NX×3) ----------
782
- Eigen::Matrix<T,NX,3 > PCt; PCt.setZero ();
783
- {
784
- const auto P_all_th = Pext.template block <NX,3 >(0 , OFF_TH);
785
- const auto P_all_aw = Pext.template block <NX,3 >(0 , off_aw);
786
- PCt.noalias () += P_all_th * J_att.transpose ();
787
- PCt.noalias () += P_all_aw * J_aw.transpose ();
723
+ Vector3 inno = acc_meas - v1hat;
788
724
789
- if constexpr (with_accel_bias) {
790
- const auto P_all_ba = Pext.template block <NX,3 >(0 , off_ba);
791
- PCt.noalias () += P_all_ba * J_ba.transpose (); // J_ba = I
792
- }
793
- }
725
+ Matrix3 S_mat = Cext * Pext * Cext.transpose () + Racc;
726
+ Matrix<T, NX, 3 > PCt = Pext * Cext.transpose ();
794
727
795
- // ---------- K = PCᵀ S^{-1} ----------
796
728
Eigen::LDLT<Matrix3> ldlt (S_mat);
797
729
if (ldlt.info () != Eigen::Success) {
798
- S_mat += Matrix3::Identity ()
799
- * std::max (std::numeric_limits<T>::epsilon (), T (1e-6 ) * Racc.norm ());
730
+ S_mat += Matrix3::Identity () * std::max (std::numeric_limits<T>::epsilon (), T (1e-6 ) * Racc.norm ());
800
731
ldlt.compute (S_mat);
801
732
if (ldlt.info () != Eigen::Success) return ;
802
733
}
803
- const Eigen::Matrix<T,NX,3 > K = PCt * ldlt.solve (Matrix3::Identity ());
804
-
805
- // ---------- State & Covariance (Joseph, full P) ----------
806
- xext.noalias () += K * r;
807
-
808
- const Eigen::Matrix<T,3 ,NX> CP = PCt.transpose (); // CP = (PCᵀ)ᵀ
809
- const Eigen::Matrix<T,NX,NX> KCP = K * CP;
810
- const Eigen::Matrix<T,NX,NX> KSKt = K * S_mat * K.transpose ();
734
+ Matrix<T, NX, 3 > K = PCt * ldlt.solve (Matrix3::Identity ());
811
735
812
- Pext.noalias () -= KCP;
813
- Pext.noalias () -= KCP.transpose ();
814
- Pext.noalias () += KSKt;
736
+ xext.noalias () += K * inno;
815
737
738
+ MatrixNX I = MatrixNX::Identity ();
739
+ Pext = (I - K * Cext) * Pext * (I - K * Cext).transpose () + K * Racc * K.transpose ();
816
740
Pext = T (0.5 ) * (Pext + Pext.transpose ());
741
+
817
742
applyQuaternionCorrectionFromErrorState ();
818
- Pbase = Pext.topLeftCorner (BASE_N, BASE_N);
819
743
}
820
744
821
745
template <typename T, bool with_gyro_bias, bool with_accel_bias>
0 commit comments