Line | Branch | Exec | Source |
---|---|---|---|
1 | // This file is part of INSTINCT, the INS Toolkit for Integrated | ||
2 | // Navigation Concepts and Training by the Institute of Navigation of | ||
3 | // the University of Stuttgart, Germany. | ||
4 | // | ||
5 | // This Source Code Form is subject to the terms of the Mozilla Public | ||
6 | // License, v. 2.0. If a copy of the MPL was not distributed with this | ||
7 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
8 | |||
9 | /// @file MotionModel.hpp | ||
10 | /// @brief Motion System Model | ||
11 | /// @author T. Topp (topp@ins.uni-stuttgart.de) | ||
12 | /// @date 2024-08-20 | ||
13 | |||
14 | #pragma once | ||
15 | |||
16 | #include <array> | ||
17 | #include <cmath> | ||
18 | #include "internal/gui/widgets/HelpMarker.hpp" | ||
19 | #include <Eigen/Core> | ||
20 | #include <cstdint> | ||
21 | #include <imgui.h> | ||
22 | |||
23 | #include "Navigation/GNSS/SystemModel/Units.hpp" | ||
24 | #include "Navigation/Math/KalmanFilter.hpp" | ||
25 | #include "Navigation/Math/VanLoan.hpp" | ||
26 | #include "Navigation/GNSS/SystemModel/SystemModel.hpp" | ||
27 | #include "Navigation/Transformations/CoordinateFrames.hpp" | ||
28 | #include "Units.hpp" | ||
29 | #include "util/Container/KeyedMatrix.hpp" | ||
30 | #include "internal/gui/widgets/InputWithUnit.hpp" | ||
31 | |||
32 | namespace NAV | ||
33 | { | ||
34 | |||
35 | namespace Keys | ||
36 | { | ||
37 | |||
38 | /// Keys used in the model | ||
39 | enum MotionModelKey : uint8_t | ||
40 | { | ||
41 | PosX, ///< Position ECEF_X [m] | ||
42 | PosY, ///< Position ECEF_Y [m] | ||
43 | PosZ, ///< Position ECEF_Z [m] | ||
44 | VelX, ///< Velocity ECEF_X [m/s] | ||
45 | VelY, ///< Velocity ECEF_Y [m/s] | ||
46 | VelZ, ///< Velocity ECEF_Z [m/s] | ||
47 | MotionModelKey_COUNT, ///< Count | ||
48 | }; | ||
49 | |||
50 | /// @brief All position keys | ||
51 | template<typename StateKeyType> | ||
52 | const std::vector<StateKeyType> Pos = { Keys::PosX, Keys::PosY, Keys::PosZ }; | ||
53 | /// @brief All velocity keys | ||
54 | template<typename StateKeyType> | ||
55 | const std::vector<StateKeyType> Vel = { Keys::VelX, Keys::VelY, Keys::VelZ }; | ||
56 | /// @brief Vector with all position and velocity state keys | ||
57 | template<typename StateKeyType> | ||
58 | const std::vector<StateKeyType> PosVel = { Keys::PosX, Keys::PosY, Keys::PosZ, | ||
59 | Keys::VelX, Keys::VelY, Keys::VelZ }; | ||
60 | |||
61 | } // namespace Keys | ||
62 | |||
63 | /// Motion System Model | ||
64 | template<typename StateKeyType> | ||
65 | class MotionModel | ||
66 | { | ||
67 | public: | ||
68 | /// @brief Initializes the motion model | ||
69 | /// @param[in, out] F System model matrix | ||
70 | /// @param[in, out] W Noise scale matrix | ||
71 | template<typename Scalar, int Size> | ||
72 | 24 | void initialize(KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& F, | |
73 | KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& W) | ||
74 | { | ||
75 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 24 times.
|
144 | for (size_t i = 0; i < _gui_covarianceAccel.size(); i++) |
76 | { | ||
77 | // Covariance of the acceleration 𝜎_a due to user motion in horizontal and vertical component [m²/s³] | ||
78 | 48 | _covarianceAccel.at(i) = convertUnit(_gui_covarianceAccel.at(i), _gui_covarianceAccelUnit); | |
79 | } | ||
80 | |||
81 |
3/6✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
|
24 | F.template block<3>(Pos, Vel) = Eigen::Matrix3d::Identity(); |
82 |
3/6✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 24 times.
✗ Branch 11 not taken.
|
24 | W.template block<3>(Vel, Vel) = Eigen::DiagonalMatrix<double, 3>(_covarianceAccel[0], _covarianceAccel[0], _covarianceAccel[1]); |
83 | 24 | } | |
84 | |||
85 | /// @brief Updates the provided Phi, Q and G matrix | ||
86 | /// @param[in, out] Phi State transition matrix | ||
87 | /// @param[in, out] Q System/Process noise covariance matrix | ||
88 | /// @param[in, out] G Noise input matrix | ||
89 | /// @param[in] F System model matrix | ||
90 | /// @param[in] W Noise scale matrix | ||
91 | /// @param[in] dt Time step size in [s] | ||
92 | /// @param[in] latitude Latitude [rad] | ||
93 | /// @param[in] longitude Longitude [rad] | ||
94 | /// @param[in] algorithm Algorithm to use for the calculation | ||
95 | template<typename Scalar, int Size> | ||
96 | ✗ | void updatePhiAndQ(KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& Phi, | |
97 | KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& Q, | ||
98 | KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& G, | ||
99 | const KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& F, | ||
100 | const KeyedMatrix<Scalar, StateKeyType, StateKeyType, Size, Size>& W, | ||
101 | double dt, | ||
102 | const double& latitude, | ||
103 | const double& longitude, | ||
104 | SystemModelCalcAlgorithm algorithm) | ||
105 | { | ||
106 | ✗ | if (algorithm == SystemModelCalcAlgorithm::VanLoan) | |
107 | { | ||
108 | ✗ | G.template block<3>(Vel, Vel) = trafo::e_Quat_n(latitude, longitude).toRotationMatrix(); | |
109 | ✗ | auto [PhiMot, QMot] = NAV::calcPhiAndQWithVanLoanMethod( | |
110 | ✗ | F.template block<6>(PosVel, PosVel), | |
111 | ✗ | G.template block<6>(PosVel, PosVel), | |
112 | ✗ | W.template block<6>(PosVel, PosVel), | |
113 | dt); | ||
114 | ✗ | Phi.template block<6>(PosVel, PosVel) = PhiMot; | |
115 | ✗ | Q.template block<6>(PosVel, PosVel) = QMot; | |
116 | } | ||
117 | else // QCalculationAlgorithm::Taylor1 | ||
118 | { | ||
119 | ✗ | Phi.template block<6>(PosVel, PosVel) = transitionMatrix_Phi_Taylor(F.template block<6>(PosVel, PosVel), dt, 1); | |
120 | ✗ | Q.template block<6>(PosVel, PosVel) = calcProcessNoiseMatrixTaylor(dt, latitude, longitude)(all, all); | |
121 | } | ||
122 | ✗ | } | |
123 | |||
124 | /// @brief Calculates the state transition matrix (𝚽) and the process noise covariance matrix (𝐐) | ||
125 | /// @param[in] dt Time step size in [s] | ||
126 | /// @param[in] latitude Latitude [rad] | ||
127 | /// @param[in] longitude Longitude [rad] | ||
128 | /// @param[in] algorithm Algorithm to use for the calculation | ||
129 | /// @return Phi and Q matrix | ||
130 | [[nodiscard]] std::pair<KeyedMatrix6d<StateKeyType>, KeyedMatrix6d<StateKeyType>> | ||
131 | calcPhiAndQ(double dt, const double& latitude, const double& longitude, SystemModelCalcAlgorithm algorithm) | ||
132 | { | ||
133 | KeyedMatrix6d<StateKeyType> F(Eigen::Matrix6d::Zero(), PosVel, PosVel); | ||
134 | KeyedMatrix6d<StateKeyType> G(Eigen::Matrix6d::Zero(), PosVel, PosVel); | ||
135 | KeyedMatrix6d<StateKeyType> W(Eigen::Matrix6d::Zero(), PosVel, PosVel); | ||
136 | initialize(F, W); | ||
137 | |||
138 | KeyedMatrix6d<StateKeyType> Phi(Eigen::Matrix6d::Zero(), PosVel, PosVel); | ||
139 | KeyedMatrix6d<StateKeyType> Q(Eigen::Matrix6d::Zero(), PosVel, PosVel); | ||
140 | updatePhiAndQ(Phi, Q, G, F, W, dt, latitude, longitude, algorithm); | ||
141 | |||
142 | return { Phi, Q }; | ||
143 | } | ||
144 | |||
145 | /// @brief Shows a GUI | ||
146 | /// @param[in] itemWidth Width of the space for the config items | ||
147 | /// @param[in] unitWidth Width of the units | ||
148 | /// @param[in] id Unique id for ImGui | ||
149 | /// @return True if something was changed | ||
150 | ✗ | bool ShowGui(float itemWidth, float unitWidth, const char* id) | |
151 | { | ||
152 | ✗ | bool changed = false; | |
153 | |||
154 | ✗ | if (gui::widgets::InputDouble2WithUnit(fmt::format("{} of the acceleration due to user motion (Hor/Ver)##{}", | |
155 | ✗ | _gui_covarianceAccelUnit == Units::CovarianceAccelUnits::m_sqrts3 | |
156 | ✗ | ? "StdDev" | |
157 | : "Variance", | ||
158 | id) | ||
159 | .c_str(), | ||
160 | ✗ | itemWidth, unitWidth, _gui_covarianceAccel.data(), _gui_covarianceAccelUnit, | |
161 | MakeComboItems<Units::CovarianceAccelUnits>().c_str(), //"m/√(s^3)\0m^2/s^3\0\0", | ||
162 | "%.2e", ImGuiInputTextFlags_CharsScientific)) | ||
163 | { | ||
164 | ✗ | LOG_DEBUG("{}: _gui_covarianceAccel changed to {}", id, fmt::join(_gui_covarianceAccel.begin(), _gui_covarianceAccel.end(), ", ")); | |
165 | ✗ | LOG_DEBUG("{}: _gui_covarianceAccelUnit changed to {}", id, to_string(_gui_covarianceAccelUnit)); | |
166 | ✗ | changed = true; | |
167 | } | ||
168 | ✗ | ImGui::SameLine(); | |
169 | ✗ | gui::widgets::HelpMarker(fmt::format("Suitable values for the horizontal acceleration are around\n" | |
170 | "- 1 {} for a pedestrian or ship,\n" | ||
171 | "- {} {} for a car, and\n" | ||
172 | "- {} {} for a military aircraft.\n" | ||
173 | "The vertical acceleration PSD is usually smaller.", | ||
174 | to_string(_gui_covarianceAccelUnit), | ||
175 | ✗ | _gui_covarianceAccelUnit == Units::CovarianceAccelUnits::m_sqrts3 ? 3 : 10, | |
176 | to_string(_gui_covarianceAccelUnit), | ||
177 | ✗ | _gui_covarianceAccelUnit == Units::CovarianceAccelUnits::m_sqrts3 ? 10 : 100, | |
178 | to_string(_gui_covarianceAccelUnit)) | ||
179 | .c_str()); | ||
180 | |||
181 | ✗ | return changed; | |
182 | } | ||
183 | |||
184 | private: | ||
185 | /// @brief All position keys | ||
186 | const std::vector<StateKeyType>& Pos = Keys::Pos<StateKeyType>; | ||
187 | /// @brief All velocity keys | ||
188 | const std::vector<StateKeyType>& Vel = Keys::Vel<StateKeyType>; | ||
189 | /// @brief All position and velocity keys | ||
190 | const std::vector<StateKeyType>& PosVel = Keys::PosVel<StateKeyType>; | ||
191 | |||
192 | /// @brief Calculates the process noise matrix Q | ||
193 | /// @param[in] dt Time step [s] | ||
194 | /// @param[in] latitude Latitude [rad] | ||
195 | /// @param[in] longitude Longitude [rad] | ||
196 | /// @note See \cite Groves2013 Groves, ch. 9.4.2.2, eq. 9.152, p. 417 | ||
197 | [[nodiscard]] KeyedMatrix6d<StateKeyType> | ||
198 | ✗ | calcProcessNoiseMatrixTaylor(double dt, double latitude, double longitude) const | |
199 | { | ||
200 | // Scaling matrix in n-frame | ||
201 | ✗ | Eigen::Matrix3d a_S_n = Eigen::DiagonalMatrix<double, 3>(_covarianceAccel[0], | |
202 | _covarianceAccel[0], | ||
203 | _covarianceAccel[1]); | ||
204 | // Scaling matrix in e-frame | ||
205 | ✗ | Eigen::Matrix3d a_S_e = trafo::e_Quat_n(latitude, longitude) * a_S_n * trafo::n_Quat_e(latitude, longitude); | |
206 | |||
207 | ✗ | double dt2 = std::pow(dt, 2); | |
208 | ✗ | double dt3 = dt2 * dt; | |
209 | |||
210 | ✗ | KeyedMatrix6d<StateKeyType> Q(Eigen::Matrix6d::Zero(), PosVel, PosVel); | |
211 | |||
212 | // Groves ch. 9.4.2.2, eq. 9.152, p. 417 | ||
213 | ✗ | Q.template block<3>(Pos, Pos) = dt3 / 3.0 * a_S_e; | |
214 | ✗ | Q.template block<3>(Pos, Vel) = dt2 / 2.0 * a_S_e; | |
215 | ✗ | Q.template block<3>(Vel, Pos) = Q.template block<3>(Pos, Vel).transpose(); | |
216 | ✗ | Q.template block<3>(Vel, Vel) = dt * a_S_e; | |
217 | |||
218 | ✗ | return Q; | |
219 | ✗ | } | |
220 | |||
221 | /// Gui selection for the Unit of the input covarianceAccel parameter for the StDev due to acceleration due to user motion | ||
222 | Units::CovarianceAccelUnits _gui_covarianceAccelUnit = Units::CovarianceAccelUnits::m_sqrts3; | ||
223 | /// @brief GUI selection for the Standard deviation of the acceleration 𝜎_a due to user motion in horizontal and vertical component | ||
224 | /// @note See Groves (2013) eq. (9.156) | ||
225 | std::array<double, 2> _gui_covarianceAccel = { 3.0, 1.5 } /* [ m / √(s^3) ] */; | ||
226 | |||
227 | /// @brief Covariance of the acceleration 𝜎_a due to user motion in horizontal and vertical component [m²/s³] | ||
228 | std::array<double, 2> _covarianceAccel = { 3.0, 1.5 }; | ||
229 | |||
230 | /// @brief Converts the provided data into a json object | ||
231 | /// @param[out] j Json object which gets filled with the info | ||
232 | /// @param[in] data Data to convert into json | ||
233 | ✗ | friend void to_json(json& j, const MotionModel& data) | |
234 | { | ||
235 | ✗ | j = { | |
236 | ✗ | { "covarianceAccelUnit", data._gui_covarianceAccelUnit }, | |
237 | ✗ | { "covarianceAccel", data._gui_covarianceAccel }, | |
238 | }; | ||
239 | ✗ | } | |
240 | /// @brief Converts the provided json object into the data object | ||
241 | /// @param[in] j Json object with the needed values | ||
242 | /// @param[out] data Object to fill from the json | ||
243 | 1 | friend void from_json(const json& j, MotionModel& data) | |
244 | { | ||
245 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (j.contains("covarianceAccelUnit")) { j.at("covarianceAccelUnit").get_to(data._gui_covarianceAccelUnit); } |
246 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (j.contains("covarianceAccel")) { j.at("covarianceAccel").get_to(data._gui_covarianceAccel); } |
247 | 1 | } | |
248 | }; | ||
249 | |||
250 | } // namespace NAV | ||
251 | |||
252 | /// @brief Stream insertion operator overload | ||
253 | /// @param[in, out] os Output stream object to stream the time into | ||
254 | /// @param[in] obj Object to print | ||
255 | /// @return Returns the output stream object in order to chain stream insertions | ||
256 | std::ostream& operator<<(std::ostream& os, const NAV::Keys::MotionModelKey& obj); | ||
257 | |||
258 | #ifndef DOXYGEN_IGNORE | ||
259 | |||
260 | /// @brief Formatter | ||
261 | template<> | ||
262 | struct fmt::formatter<NAV::Keys::MotionModelKey> : fmt::formatter<const char*> | ||
263 | { | ||
264 | /// @brief Defines how to format structs | ||
265 | /// @param[in] state Struct to format | ||
266 | /// @param[in, out] ctx Format context | ||
267 | /// @return Output iterator | ||
268 | template<typename FormatContext> | ||
269 | ✗ | auto format(const NAV::Keys::MotionModelKey& state, FormatContext& ctx) const | |
270 | { | ||
271 | using namespace NAV::Keys; // NOLINT(google-build-using-namespace) | ||
272 | |||
273 | ✗ | switch (state) | |
274 | { | ||
275 | ✗ | case PosX: | |
276 | ✗ | return fmt::formatter<const char*>::format("PosX", ctx); | |
277 | ✗ | case PosY: | |
278 | ✗ | return fmt::formatter<const char*>::format("PosY", ctx); | |
279 | ✗ | case PosZ: | |
280 | ✗ | return fmt::formatter<const char*>::format("PosZ", ctx); | |
281 | ✗ | case VelX: | |
282 | ✗ | return fmt::formatter<const char*>::format("VelX", ctx); | |
283 | ✗ | case VelY: | |
284 | ✗ | return fmt::formatter<const char*>::format("VelY", ctx); | |
285 | ✗ | case VelZ: | |
286 | ✗ | return fmt::formatter<const char*>::format("VelZ", ctx); | |
287 | ✗ | case MotionModelKey_COUNT: | |
288 | ✗ | return fmt::formatter<const char*>::format("MotionModelKey_COUNT", ctx); | |
289 | } | ||
290 | |||
291 | ✗ | return fmt::formatter<const char*>::format("ERROR", ctx); | |
292 | } | ||
293 | }; | ||
294 | |||
295 | #endif | ||
296 |