WarpTwin
Documentation for WarpTwin models and classes.
Loading...
Searching...
No Matches
NonlinearLeastSquares.hpp
Go to the documentation of this file.
1/******************************************************************************
2* Copyright (c) ATTX LLC 2024. All Rights Reserved.
3*
4* This software and associated documentation (the "Software") are the
5* proprietary and confidential information of ATTX, LLC. The Software is
6* furnished under a license agreement between ATTX and the user organization
7* and may be used or copied only in accordance with the terms of the agreement.
8* Refer to 'license/attx_license.adoc' for standard license terms.
9*
10* EXPORT CONTROL NOTICE: THIS SOFTWARE MAY INCLUDE CONTENT CONTROLLED UNDER THE
11* INTERNATIONAL TRAFFIC IN ARMS REGULATIONS (ITAR) OR THE EXPORT ADMINISTRATION
12* REGULATIONS (EAR99). No part of the Software may be used, reproduced, or
13* transmitted in any form or by any means, for any purpose, without the express
14* written permission of ATTX, LLC.
15******************************************************************************/
16
17#ifndef GNC_NAVIGATION_EKF_NONLINEAR_LEAST_SQUARES_HPP
18#define GNC_NAVIGATION_EKF_NONLINEAR_LEAST_SQUARES_HPP
19
20#include "core/Matrix.hpp"
22#include "core/matrixmath.hpp"
23#include "core/vectormath.hpp"
29
30namespace warpos {
31
48 template <uint32 N, uint32 M, uint32 O>
50 public:
55
72 int16 solve(const std::array<floating_point, N> &initial_guess,
75 const std::array<floating_point, M> *measurements_pointer,
76 const floating_point *time_pointer,
77 const std::array<floating_point, O> *obs_state_pointer,
78 const uint32 num_measurements,
79 const uint32 max_iterations,
80 const floating_point tolerance,
81 const floating_point max_step_size,
82 clockwerk::Matrix<N, N> *covariance_estimate,
83 std::array<floating_point, N> *state_estimate);
84 private:
85 std::array<floating_point, N + N*N> _full_state_initial;
86 std::array<floating_point, N + N*N> _full_state_post;
87
88 floating_point _max_shift;
89 uint32 _num_iter;
90 int16 _error;
91 uint32 _meas_idx;
92
93 // Time values for measurement times
94 floating_point t_start;
95 floating_point t_current;
96 floating_point t_next;
97
98 // STM
100 clockwerk::Matrix<N, N> _phi_transpose;
101
102 // Information matrix and vector
103 clockwerk::Matrix<N, N> _tmp_chol;
104 clockwerk::Matrix<N, N> _tmp_chol_inv;
105 clockwerk::Matrix<N, N> _I_matrix;
109 std::array<floating_point, N> _dx_plus;
110
111 // Current state estimate
112 std::array<floating_point, N> _x0;
113 std::array<floating_point, N> _x_prop;
114
115 // Measurement noise covariance inverse
117
118 // Residual storage
119 std::array<floating_point, M> _residual;
120
121 // H matrix storage
122 std::array<floating_point, M*N> _tmp_H_array;
124 clockwerk::Matrix<N, M> _H_transpose;
125
126 // Temporary storage for Cholesky
127 floating_point _I_flat[N * N];
128 floating_point _y_temp[N];
129
130 // Temporary calculation matrices
131 clockwerk::Matrix<N, N> _tmp_n_n_1;
132 clockwerk::Matrix<N, N> _tmp_n_n_2;
133 clockwerk::Matrix<N, M> _tmp_n_m_1;
134 clockwerk::Matrix<M, N> _tmp_m_n_1;
135 clockwerk::Matrix<M, M> _tmp_m_m_1;
138
143 Integrator<N + N*N>& _integrator;
144
146 Measurements<N, M, O>& _measurement;
147
149 EkfMeasurementUpdate<N, M, O> _ekf_meas_update;
150 };
151
152 template <uint32 N, uint32 M, uint32 O>
154 : _integrator(integ), _measurement(measurement_ref), _ekf_meas_update(measurement_ref) {}
155
156 template<uint32 N, uint32 M, uint32 O>
158 const std::array<floating_point, N> &initial_guess,
159 const clockwerk::Matrix<N, N> &P0,
161 const std::array<floating_point, M> *measurements_pointer,
162 const floating_point *time_pointer,
163 const std::array<floating_point, O> *obs_state_pointer,
164 const uint32 num_measurements,
165 const uint32 max_iterations,
166 const floating_point tolerance,
167 const floating_point max_step_size,
168 clockwerk::Matrix<N, N> *covariance_estimate,
169 std::array<floating_point, N> *state_estimate) {
170
171 // Verify output pointers
172 if(state_estimate == nullptr || covariance_estimate == nullptr) {
173 return ERROR_NULLPTR;
174 }
175
176 // Initialize convergence parameters
177 _max_shift = tolerance + 1.0;
178 _num_iter = 0;
179 _error = NO_ERROR;
180
181 // Initialize state estimate with initial guess
182 for(uint32 i = 0; i < N; i++) {
183 _x0[i] = initial_guess[i];
184 }
185
186 // Initialize dx_minus to zero
187 for(uint32 i = 0; i < N; i++) {
188 _dx_minus[i] = 0.0;
189 }
190
191 // Set R matrix in EKF measurement update
192 _ekf_meas_update.setMeasurementNoiseMatrix(R);
193
194 // Compute R inverse for use in iterations
195 _error = R.inverse(_R_inv);
196 if(_error) {
197 return _error;
198 }
199
200 // Compute P0 inverse for use in iterations
201 _error = P0.inverse(_P0_inv);
202 if(_error) {
203 return _error;
204 }
205
206 // Outer loop - iterate until convergence or max iterations
207 while(_max_shift > tolerance && _num_iter < max_iterations) {
208
209 // I = inv(P0) on first iteration
210 _error = P0.chol(_tmp_chol);
211 if(_error) {return _error;}
212 _error = _tmp_chol.inverse(_tmp_chol_inv);
213 if(_error) {return _error;}
214
215 // Set I and N
216 _I_matrix = _tmp_chol_inv*_tmp_chol_inv.transpose();
217 _N_vector = _I_matrix*_dx_minus;
218
219 // Inner loop - propagate through all measurements
220 for(_meas_idx = 0; _meas_idx < num_measurements; _meas_idx++) {
221
222 // Set up full state vector [x; phi(:)]
223 if(_meas_idx == 0) {
224 // Initialize phi to identity on first measurement
225 unsigned int one_idx = N;
226 for(unsigned int i = 0; i < N + N*N; i++) {
227 if(i < N) {
228 _full_state_initial[i] = _x_prop[i];
229 } else if(i == one_idx) {
230 _full_state_initial[i] = 1.0;
231 one_idx = one_idx + N + 1;
232 } else {
233 _full_state_initial[i] = 0.0;
234 }
235 _full_state_post[i] = _full_state_initial[i]; // Set post to initial in case we don't propagate
236 }
237 } else {
238 // For subsequent measurements, use accumulated state and phi from previous step
239 for(unsigned int i = 0; i < N + N*N; i++) {
240 _full_state_initial[i] = _full_state_post[i];
241 }
242 }
243
244 // Propagate state and STM from previous measurement time to current measurement time
245 if (_meas_idx == 0) {
246 t_start = time_pointer[0];
247 } else {
248 t_start = time_pointer[_meas_idx - 1];
249 }
250
251 // Propagate with max step size constraint
252 t_current = t_start;
253 while(t_current < time_pointer[_meas_idx]) {
254 if (time_pointer[_meas_idx] - t_current > max_step_size) {
255 t_next = t_current + max_step_size;
256 } else {
257 t_next = time_pointer[_meas_idx];
258 }
259
260 _error = _integrator.step(t_current, t_next, _full_state_initial, _full_state_post);
261 if(_error > 0) {
262 return _error;
263 }
264
265 // Update for next sub-step
266 for(unsigned int i = 0; i < N + N*N; i++) {
267 _full_state_initial[i] = _full_state_post[i];
268 }
269 t_current = t_next;
270 }
271
272 // Extract propagated state
273 for(uint32 i = 0; i < N; i++) {
274 _x_prop[i] = _full_state_post[i];
275 }
276
277 // Extract STM (phi)
278 _phi.setFromArray(&_full_state_post[N]);
279 _phi.transpose(_phi_transpose);
280
281 // Use EKF measurement update to generate residual
282 _error = _ekf_meas_update.generateResidual(
283 time_pointer[_meas_idx],
284 _x_prop,
285 obs_state_pointer[_meas_idx],
286 measurements_pointer[_meas_idx],
287 &_residual
288 );
289 if(_error > 0) {
290 return _error;
291 }
292
293 // Get H matrix (measurement Jacobian) from measurement reference
294 _error = _measurement.calculateMeasurementsMatrix(
295 time_pointer[_meas_idx],
296 _x_prop,
297 obs_state_pointer[_meas_idx],
298 _tmp_H_array.data()
299 );
300 if(_error) {
301 return _error;
302 }
303 _H.setFromArray(&_tmp_H_array[0]);
304 _H.transpose(_H_transpose);
305
306 // Accumulate information matrix: I = I + phi^T * H^T * R^{-1} * H * phi
307 // Step 1: H * phi -> _tmp_m_n_1
308 clockwerk::multiply(_H, _phi, _tmp_m_n_1);
309
310 // Step 2: R^{-1} * (H * phi) -> _tmp_m_n_1 (overwrite)
311 clockwerk::multiply(_R_inv, _tmp_m_n_1, _tmp_m_n_1);
312
313 // Step 3: H^T * R^{-1} * H * phi -> _tmp_n_n_1
314 clockwerk::multiply(_H_transpose, _tmp_m_n_1, _tmp_n_n_1);
315
316 // Step 4: phi^T * H^T * R^{-1} * H * phi -> _tmp_n_n_1 (overwrite)
317 clockwerk::multiply(_phi_transpose, _tmp_n_n_1, _tmp_n_n_1);
318
319 // Step 5: I = I + result
320 clockwerk::eAdd(_I_matrix, _tmp_n_n_1, _I_matrix);
321
322 // Accumulate information vector: N = N + phi^T * H^T * R^{-1} * r
323 // Step 1: R^{-1} * r -> _tmp_m_1
324 _tmp_m_1.setFromArray(&_residual[0]);
325 clockwerk::multiply(_R_inv, _tmp_m_1, _tmp_m_1);
326
327 // Step 2: H^T * R^{-1} * r -> _tmp_n_1
328 clockwerk::multiply(_H_transpose, _tmp_m_1, _tmp_n_1);
329
330 // Step 3: phi^T * H^T * R^{-1} * r -> _tmp_n_1
331 clockwerk::multiply(_phi_transpose, _tmp_n_1, _tmp_n_1);
332
333 // Step 4: N = N + result
334 for(uint32 i = 0; i < N; i++) {
335 _N_vector[i] += _tmp_n_1.get(i, 0);
336 }
337
338 }
339
340 _error = _I_matrix.chol(_tmp_chol);
341 if(_error) {return _error;}
342 _error = _tmp_chol.inverse(_tmp_chol_inv);
343 if(_error) {return _error;}
344 _tmp_n_n_1 = _tmp_chol_inv*_tmp_chol_inv.transpose();
345
346 // Compute dx_plus = P * N
347 for(uint32 i = 0; i < N; i++) {
348 _dx_plus[i] = 0.0;
349 for(uint32 j = 0; j < N; j++) {
350 _dx_plus[i] += _tmp_n_n_1[i][j]*_N_vector[j];
351 }
352 }
353
354 // Update initial guess: x0 = initial_guess + dx_plus
355 for(uint32 i = 0; i < N; i++) {
356 _x0[i] = initial_guess[i] + _dx_plus[i];
357 }
358
359 // Update dx_minus: dx_minus = dx_minus + dx_plus
360 for(uint32 i = 0; i < N; i++) {
361 _dx_minus[i] = _dx_minus[i] + _dx_plus[i];
362 }
363
364 // Calculate maximum shift for convergence check
365 _max_shift = 0.0;
366 for(uint32 i = 0; i < N; i++) {
367 floating_point abs_shift = _dx_plus[i] >= 0.0 ? _dx_plus[i] : -_dx_plus[i];
368 if(abs_shift > _max_shift) {
369 _max_shift = abs_shift;
370 }
371 }
372
373 _num_iter++;
374 }
375
376 // Set output state estimate
377 for(uint32 i = 0; i < N; i++) {
378 (*state_estimate)[i] = _x0[i]-initial_guess[i];
379 }
380
381 // Set output covariance (final P = inv(I))
382 _error = _I_matrix.chol(_tmp_chol);
383 if(_error) {return _error;}
384 _error = _tmp_chol.inverse(_tmp_chol_inv);
385 if(_error) {return _error;}
386 *covariance_estimate = _tmp_chol_inv*_tmp_chol_inv.transpose();
387
388 return NO_ERROR;
389 }
390
391}
392
393#endif
Standard vector class derived from Matrix.
Definition CartesianVector.hpp:39
Matrix math implementation.
Definition Matrix.hpp:55
int16 chol(Matrix< R, C > &retval) const
Take the cholesky decomposition of this matrix.
Definition Matrix.hpp:569
void transpose(Matrix< C, R > &result) const
Function to return the transpose of the matrix.
Definition Matrix.hpp:777
int16 inverse(Matrix< R, C > &result) const
Function to return the inverse of the matrix.
Definition Matrix.hpp:617
Generic, templated EKF measurement update class.
Definition EkfMeasurementUpdate.hpp:49
Definition Integrator.hpp:37
Definition Measurements.hpp:41
NonlinearLeastSquares(Integrator< N+N *N > &integ, Measurements< N, M, O > &measurement_ref)
Definition NonlinearLeastSquares.hpp:153
int16 solve(const std::array< floating_point, N > &initial_guess, const clockwerk::Matrix< N, N > &P0, const clockwerk::Matrix< M, M > &R, const std::array< floating_point, M > *measurements_pointer, const floating_point *time_pointer, const std::array< floating_point, O > *obs_state_pointer, const uint32 num_measurements, const uint32 max_iterations, const floating_point tolerance, const floating_point max_step_size, clockwerk::Matrix< N, N > *covariance_estimate, std::array< floating_point, N > *state_estimate)
Function to solve the nonlinear least squares problem.
Definition NonlinearLeastSquares.hpp:157
#define NO_ERROR
Error code in the case where matrix math executed successfully.
Definition clockwerkerrors.h:34
#define ERROR_NULLPTR
Error code in case of a null pointer.
Definition clockwerkerrors.h:60
Embedded linear algebra math library.
void eAdd(const Matrix< R, C > &A, const Matrix< R, C > &B, Matrix< R, C > &result)
Function to add two matrices element-wise.
Definition matrixmath.hpp:196
void multiply(const Matrix< R1, C1R2 > &A, const Matrix< C1R2, C2 > &B, Matrix< R1, C2 > &result)
Function to multiply two matrices.
Definition matrixmath.hpp:58
Definition DeadReckon.cpp:20