Brick
Loading...
Searching...
No Matches
unscented_kalman_filter.h
1#pragma once
2
3#include <functional>
4
5#include "core/math/eigen_interface.h"
6#include "core/math/numerical/numerical_integration.h"
7
8// Forward declare the sigma points class, it is at the bottom of this file.
9template <int STATES>
11
12// Forward declare the Unscented Transform function, it is after the SRUKF class itself.
13template <int COV_DIM, int STATES, int NUM_SIGMAS>
14std::tuple<EVec<COV_DIM>, EMat<COV_DIM, COV_DIM>> square_root_ut(
15 const EMat<COV_DIM, NUM_SIGMAS>& sigmas, const EVec<NUM_SIGMAS>& Wm, const EVec<NUM_SIGMAS>& Wc,
16 const std::function<EVec<COV_DIM>(const EMat<COV_DIM, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)>&
17 mean_func,
18 const std::function<EVec<COV_DIM>(const EVec<COV_DIM>&, const EVec<COV_DIM>&)>& residual_func,
19 const EMat<COV_DIM, COV_DIM>& square_root_R
20);
21
22/**
23 * Kalman filters combine predictions from a model and measurements to estimate
24 * a system's true state.
25 *
26 * The Unscented Kalman Filter is a nonlinear estimator, meaning that the model
27 * used to predict how the state changes over time can be nonlinear. The model
28 * that determines the expected measurement given the current state can also be
29 * nonlinear.
30 *
31 * At each timestep, sigma points are generated close to the mean, they are all
32 * propagated forward in time according to the nonlinear model. The Unscented
33 * Transform uses the propagated sigma points to compute the prior state and
34 * covariance.
35 *
36 * When correcting the state and covariance with a measurement, sigma points are
37 * again generated, but are transformed into the measurement space using the
38 * measurement function. A Kalman gain matrix K is then computed, and used to
39 * update the state and covariance.
40 *
41 * To read more about Kalman filters and the standard UKF read:
42 * https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python
43 *
44 * This implementation is somewhat non-standard. The square-root form of the UKF
45 * (SR-UKF) is used, and the way the sigma points are generated is different than
46 * most implementations. The square-root form is used to ensure that the
47 * covariance matrix remains positive definite.
48 *
49 * To learn more about the SR-UKF, and see the exact formulation that most of this
50 * implementation follows, read:
51 * https://www.researchgate.net/publication/3908304
52 *
53 * The sigma points are not generated symmetrically around the mean, instead they
54 * are generated as vertices of a simplex. Using N = # of states, this method uses
55 * N + 2 sigma points instead of the standard 2N + 1 sigma points. This reduces
56 * computation up to 50%. To learn more about this method, read:
57 * https://www.sciencedirect.com/science/article/pii/S0888327020308190
58 *
59 * @tparam STATES Dimension of the state vector.
60 * @tparam INPUTS Dimension of the control input vector.
61 * @tparam OUTPUTS Dimension of the measurement vector.
62 */
63template <int STATES, int INPUTS, int OUTPUTS>
65 public:
66 static constexpr int NUM_SIGMAS = STATES + 2;
67
68 using StateVector = EVec<STATES>;
69 using InputVector = EVec<INPUTS>;
70 using OutputVector = EVec<OUTPUTS>;
71
72 using StateMatrix = EMat<STATES, STATES>;
73
74 using WithInputIntegrator = std::function<EVec<STATES>(
75 const WithInputDerivative<STATES, INPUTS>& f, const EVec<STATES>& x, const EVec<INPUTS>& u,
76 const double& h
77 )>;
78
79 /**
80 * Constructs an Unscented Kalman Filter.
81 *
82 * @param f A vector valued function of x and u that returns the derivative of
83 * the state vector with respect to time.
84 * @param h A vector valued function of x and u that returns the expected
85 * measurement at the given state.
86 * @param integrator A function from "numerical_integration.h" that integrates
87 * a differential equation of the form f(x, u).
88 * @param state_stddevs Standard deviations of the states in the model.
89 * @param measurement_stddevs Standard deviations of the measurements.
90 */
92 const std::function<StateVector(const StateVector&, const InputVector&)>& f,
93 const std::function<OutputVector(const StateVector&, const InputVector&)>& h,
94 const WithInputIntegrator& integrator, const StateVector& state_stddevs,
95 const OutputVector& measurement_stddevs
96 )
97 : f_(f), h_(h), integrator_(integrator) {
98 sqrt_Q_ = state_stddevs.asDiagonal();
99 measurement_stddevs_ = measurement_stddevs;
100 mean_func_X_ = [](const EMat<STATES, NUM_SIGMAS>& sigmas,
101 const EVec<NUM_SIGMAS>& Wm) -> StateVector { return sigmas * Wm; };
102
103 mean_func_Y_ = [](const EMat<OUTPUTS, NUM_SIGMAS>& sigmas,
104 const EVec<NUM_SIGMAS>& Wc) -> OutputVector { return sigmas * Wc; };
105 residual_func_X_ = [](const StateVector& a, const StateVector& b) -> StateVector {
106 return a - b;
107 };
108 residual_func_Y_ = [](const OutputVector& a, const OutputVector& b) -> OutputVector {
109 return a - b;
110 };
111 add_func_X_ = [](const StateVector& a, const StateVector& b) -> StateVector { return a + b; };
112
113 reset();
114 }
115
116 /**
117 * Constructs an Unscented Kalman Filter with custom mean, residual, and
118 * addition functions. The most common use for these functions is when you
119 * are estimating angles whose arithmetic operations need to be wrapped.
120 *
121 * @param f A vector valued function of x and u that returns the derivative of
122 * the state vector with respect to time.
123 * @param h A vector valued function of x and u that returns the expected
124 * measurement at the given state.
125 * @param integrator A function from "numerical_integration.h" that integrates
126 * a differential equation of the form f(x, u).
127 * @param state_stddevs Standard deviations of the states in the model.
128 * @param measurement_stddevs Standard deviations of the measurements.
129 * @param mean_func_X A function that computes the mean of a matrix
130 * containing NUM_SIGMAS state sigma points with a set of weights for each.
131 * @param mean_func_Y A function that computes the mean of a matrix
132 * containing NUM_SIGMAS measurement sigma points with a set of weights for each.
133 * @param residual_func_X A function that computes the residual of two state
134 * vectors, usually by simple subtraction.
135 * @param residual_func_Y A function that computes the residual of two measurement
136 * vectors, usually by simple subtraction.
137 * @param add_funx_X A function that adds two state vectors.
138 */
140 const std::function<StateVector(const StateVector&, const InputVector&)>& f,
141 const std::function<OutputVector(const StateVector&, const InputVector&)>& h,
142 const WithInputIntegrator& integrator, const StateVector& state_stddevs,
143 const OutputVector& measurement_stddevs,
144 const std::function<StateVector(const EMat<STATES, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)>&
145 mean_func_X,
146 const std::function<OutputVector(const EMat<OUTPUTS, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)>&
147 mean_func_Y,
148 const std::function<StateVector(const StateVector&, const StateVector&)>& residual_func_X,
149 const std::function<OutputVector(const OutputVector&, const OutputVector&)>& residual_func_Y,
150 const std::function<StateVector(const StateVector&, const StateVector&)>& add_func_X
151 )
152 : f_(f),
153 h_(h),
154 integrator_(integrator),
155 mean_func_X_(mean_func_X),
156 mean_func_Y_(mean_func_Y),
157 residual_func_X_(residual_func_X),
158 residual_func_Y_(residual_func_Y),
159 add_func_X_(add_func_X) {
160 sqrt_Q_ = state_stddevs.asDiagonal();
161 measurement_stddevs_ = measurement_stddevs;
162
163 reset();
164 }
165
166 /// Returns the square-root covariance matrix S.
167 StateMatrix S() const { return S_; }
168
169 /**
170 * Returns one element of the square-root covariance matrix S.
171 *
172 * @param i Row of S.
173 * @param j Column of S.
174 */
175 double S(int i, int j) const { return S_(i, j); }
176
177 /**
178 * Set the current square-root covariance matrix S.
179 *
180 * @param S The new square-root covariance matrix S.
181 */
182 void set_S(const StateMatrix& S) { S_ = S; }
183
184 /// Returns the reconstructed covariance matrix P.
185 StateMatrix P() const { return S_ * S_.transpose(); }
186
187 /**
188 * Set the current square-root covariance matrix S to the square-root of P.
189 *
190 * @param P The covariance matrix P.
191 */
192 void set_P(const StateMatrix& P) { S_ = P.llt().matrixL(); }
193
194 /// Returns the current state estimate x-hat.
195 StateVector xhat() const { return xhat_; }
196
197 /**
198 * Returns one element of the current state estimate x-hat.
199 *
200 * @param i Row of x-hat.
201 */
202 double xhat(int i) const { return xhat_(i); }
203
204 /// Set the current state estimate x-hat.
205 void set_xhat(const StateVector& xhat) { xhat_ = xhat; }
206
207 /**
208 * Set one element of the current state estimate x-hat.
209 *
210 * @param i Row of x-hat.
211 */
212 void set_xhat(int i, double value) { xhat_(i) = value; }
213
214 /// Resets the filter.
215 void reset() {
216 xhat_.setZero();
217 S_.setZero();
218 sigmas_F_.setZero();
219 }
220
221 /**
222 * Projects the state into the future by dt seconds with control input u.
223 *
224 * @param u The control input.
225 * @param dt The timestep in seconds.
226 */
227 void predict(const InputVector& u, double dt) {
228 // Our noise is continuous, so we need to discretize
229 const EMat<STATES, STATES> Q = sqrt_Q_ * sqrt(dt);
230 /*
231 * Generate sigma points around the state mean
232 *
233 * equation (17)
234 */
235 EMat<STATES, NUM_SIGMAS> sigmas = pts_.square_root_sigma_points(xhat_, S_);
236
237 /*
238 * Project each sigma point forward in time according to the
239 * dynamics f(x, u)
240 *
241 * sigmas = 𝒳ₖ₋₁
242 * sigmasF = 𝒳ₖ,ₖ₋₁ or just 𝒳 for readability
243 *
244 *
245 * equation (18)
246 */
247 for (int i = 0; i < NUM_SIGMAS; ++i) {
248 StateVector x = sigmas.template block<STATES, 1>(0, i);
249 sigmas_F_.template block<STATES, 1>(0, i) = integrator_(f_, x, u, dt);
250 }
251 /*
252 * Pass the predicted sigmas (𝒳) through the Unscented Transform
253 * to compute the prior state mean and covariance
254 *
255 * equations (18) (19) and (20)
256 */
257 auto [xhat, S] = square_root_ut<STATES, STATES>(
258 sigmas_F_, pts_.Wm(), pts_.Wc(), mean_func_X_, residual_func_X_,
259 Q.template triangularView<Eigen::Lower>()
260 );
261
262 xhat_ = xhat;
263 S_ = S;
264 }
265
266 /**
267 * Correct the state estimate using the measurements in y.
268 *
269 * @param u The control input used in the last predict step.
270 * @param y The vector of measurements.
271 */
272 void correct(const InputVector& u, const OutputVector& y) {
274 u, y, h_, measurement_stddevs_, mean_func_Y_, residual_func_Y_, residual_func_X_,
275 add_func_X_
276 );
277 }
278
279 /**
280 * Correct the state estimate using the measurements in y, and custom standard
281 * deviations. This is useful for when the noise in the measurements vary.
282 *
283 * @param u The control input used in the last predict step.
284 * @param y The vector of measurements.
285 * @param measurement_stddevs The vector of standard deviations for each
286 * measurement to be used for this correct step.
287 */
289 const InputVector& u, const OutputVector& y, const EVec<OUTPUTS>& measurement_stddevs
290 ) {
292 u, y, h_, measurement_stddevs, mean_func_Y_, residual_func_Y_, residual_func_X_, add_func_X_
293 );
294 }
295
296 /**
297 * Correct the state estimate using the measurements in y, a custom measurement
298 * function, and custom standard deviations. This is useful for when a different
299 * set of measurements are being applied.
300 *
301 * @param u The control input used in the last predict step.
302 * @param y The vector of measurements.
303 * @param h A vector valued function of x and u that returns the expected
304 * measurement at the given state.
305 * @param measurement_stddevs The vector of standard deviations for each
306 * measurement to be used for this correct step.
307 */
308 template <int ROWS>
310 const InputVector& u, const EVec<ROWS>& y,
311 const std::function<EVec<ROWS>(const StateVector&, const InputVector&)>& h,
312 const EVec<ROWS>& measurement_stddevs
313 ) {
314 auto mean_func_Y = [](const EMat<OUTPUTS, NUM_SIGMAS>& sigmas,
315 const EVec<NUM_SIGMAS>& Wc) -> EVec<ROWS> { return sigmas * Wc; };
316 auto residual_func_X = [](const StateVector& a, const StateVector& b) -> StateVector {
317 return a - b;
318 };
319 auto residual_func_Y = [](const EVec<ROWS>& a, const EVec<ROWS>& b) -> EVec<ROWS> {
320 return a - b;
321 };
322 auto add_func_X = [](const StateVector& a, const StateVector& b) -> StateVector {
323 return a + b;
324 };
325
327 u, y, h, measurement_stddevs, mean_func_Y, residual_func_Y, residual_func_X, add_func_X
328 );
329 }
330
331 /**
332 * Correct the state estimate using the measurements in y, a custom measurement
333 * function, custom standard deviations, and custom mean, residual, and addition
334 * functions. This is useful for when a different set of measurements are being
335 * applied, and they require custom arithmetic functions.
336 *
337 * @param u The control input used in the last predict step.
338 * @param y The vector of measurements.
339 * @param h A vector valued function of x and u that returns the expected
340 * measurement at the given state.
341 * @param measurement_stddevs The vector of standard deviations for each
342 * measurement to be used for this correct step.
343 * @param mean_func_Y A function that computes the mean of a matrix
344 * containing NUM_SIGMAS measurement sigma points with a set of weights for each.
345 * @param residual_func_X A function that computes the residual of two state
346 * vectors, usually by simple subtraction.
347 * @param residual_func_Y A function that computes the residual of two measurement
348 * vectors, usually by simple subtraction.
349 * @param add_funx_X A function that adds two state vectors.
350 */
351 template <int ROWS>
353 const InputVector& u, const EVec<ROWS>& y,
354 const std::function<EVec<ROWS>(const StateVector&, const InputVector&)>& h,
355 const EVec<ROWS> measurement_stddevs,
356 const std::function<EVec<ROWS>(const EMat<ROWS, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)>&
357 mean_func_Y,
358 const std::function<EVec<ROWS>(const EVec<ROWS>&, const EVec<ROWS>&)>& residual_func_Y,
359 const std::function<StateVector(const StateVector&, const StateVector&)>& residual_func_X,
360 const std::function<StateVector(const StateVector&, const StateVector&)>& add_func_X
361 ) {
362 EMat<ROWS, ROWS> sqrt_R = measurement_stddevs.asDiagonal();
363 /*
364 * Generate new sigma points from the prior mean and covariance
365 * and transform them into measurement space using h(x, u)
366 *
367 * sigmas = 𝒳
368 * sigmasH = 𝒴
369 *
370 * This differs from equation (22) which uses
371 * the prior sigma points, regenerating them allows
372 * multiple measurement updates per time update
373 */
374 EMat<ROWS, NUM_SIGMAS> sigmas_H;
375 EMat<STATES, NUM_SIGMAS> sigmas = pts_.square_root_sigma_points(xhat_, S_);
376 for (int i = 0; i < NUM_SIGMAS; ++i) {
377 sigmas_H.template block<ROWS, 1>(0, i) = h(sigmas.template block<STATES, 1>(0, i), u);
378 }
379 /*
380 * Pass the predicted measurement sigmas through the Unscented Transform
381 * to compute the mean predicted measurement and square-root innovation
382 * covariance.
383 *
384 * equations (23) (24) and (25)
385 */
386 auto [yhat, Sy] = square_root_ut<ROWS, STATES, NUM_SIGMAS>(
387 sigmas_H, pts_.Wm(), pts_.Wc(), mean_func_Y, residual_func_Y,
388 sqrt_R.template triangularView<Eigen::Lower>()
389 );
390 /*
391 * Compute cross covariance of the predicted state and measurement sigma
392 * points given as:
393 *
394 * L+1
395 * P_{xy} = Σ Wᵢ⁽ᶜ⁾[𝒳ᵢ - x̂][𝒴ᵢ - ŷ⁻]ᵀ
396 * i=0
397 *
398 * equation (26)
399 */
400 EMat<STATES, ROWS> Pxy;
401 Pxy.setZero();
402 for (int i = 0; i < NUM_SIGMAS; ++i) {
403 Pxy += pts_.Wc(i) * (residual_func_X(sigmas_F_.template block<STATES, 1>(0, i), xhat_)) *
404 (residual_func_Y(sigmas_H.template block<ROWS, 1>(0, i), yhat)).transpose();
405 }
406 /*
407 * Compute the Kalman gain. We use Eigen's forward and backward substitution
408 * to solve. The equation in the paper uses MATLAB's / operator, but Eigen's
409 * solvers act like the \ operator, so we need to rearrange the equation to
410 * use those.
411 *
412 * K = (P_{xy} / S_{y}ᵀ) / S_{y}
413 * K = (S_{y} \ P_{xy})ᵀ / S_{y}
414 * K = (S_{y}ᵀ \ (S_{y} \ P_{xy}ᵀ))ᵀ
415 *
416 * equation (27)
417 */
418 EMat<STATES, ROWS> K = (Sy.transpose().template triangularView<Eigen::Upper>().solve(
419 Sy.template triangularView<Eigen::Lower>().solve(Pxy.transpose())
420 ))
421 .transpose();
422
423 /*
424 * Compute the posterior state mean
425 *
426 * x̂ = x̂⁻ + K(y − ŷ⁻)
427 *
428 * second part of equation (27)
429 */
430 xhat_ = add_func_X(xhat_, K * residual_func_Y(y, yhat));
431 /*
432 * Compute the intermediate matrix U for downdating
433 * the square-root covariance
434 *
435 * equation (28)
436 */
437 EMat<STATES, ROWS> U = K * Sy;
438 /*
439 * Downdate the posterior square-root covariance
440 *
441 * equation (29)
442 */
443 for (int i = 0; i < ROWS; i++) {
444 Eigen::internal::llt_inplace<double, Eigen::Lower>::rankUpdate(
445 S_, U.template block<STATES, 1>(0, i), -1
446 );
447 }
448 }
449
450 private:
451 std::function<StateVector(const StateVector&, const InputVector&)> f_;
452 std::function<OutputVector(const StateVector&, const InputVector&)> h_;
453 std::function<StateVector(const EMat<STATES, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)> mean_func_X_;
454 std::function<OutputVector(const EMat<OUTPUTS, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)>
455 mean_func_Y_;
456 std::function<StateVector(const StateVector&, const StateVector&)> residual_func_X_;
457 std::function<OutputVector(const OutputVector&, const OutputVector&)> residual_func_Y_;
458 std::function<StateVector(const StateVector&, const StateVector&)> add_func_X_;
459 StateVector xhat_;
460 StateMatrix S_;
461 StateMatrix sqrt_Q_;
462 EVec<OUTPUTS> measurement_stddevs_;
463 EMat<STATES, NUM_SIGMAS> sigmas_F_;
464
466
467 WithInputIntegrator integrator_;
468};
469
470/**
471 * Computes the Unscented Transform of a set of sigma points and their weights.
472 * The mean and square-root covariance of the sigma points are returned in a tuple.
473 *
474 * @tparam COV_DIM Dimension of the covariance of the sigma points after they are
475 * passed through a transforming function.
476 * @tparam STATES Dimension of the state vector.
477 *
478 * @param sigmas Matrix containing the sigma points, each column is one sigma point.
479 * @param Wm The weights for the mean.
480 * @param Wc The weights for the covariance.
481 * @param mean_func A function that computes the mean of NUM_SIGMAS sigma points
482 * using a given set of weights.
483 * @param residual_func A function that computes the residual of two sigma points.
484 * This is usually a simple subtraction.
485 * @param sqrt_R Square-root of the noise covariance of the sigma points.
486 *
487 * @return Tuple of x, and S, the mean and square-root covariance of the sigma points.
488 */
489template <int COV_DIM, int STATES, int NUM_SIGMAS>
490std::tuple<EVec<COV_DIM>, EMat<COV_DIM, COV_DIM>> square_root_ut(
491 const EMat<COV_DIM, NUM_SIGMAS>& sigmas, const EVec<NUM_SIGMAS>& Wm, const EVec<NUM_SIGMAS>& Wc,
492 const std::function<EVec<COV_DIM>(const EMat<COV_DIM, NUM_SIGMAS>&, const EVec<NUM_SIGMAS>&)>&
493 mean_func,
494 const std::function<EVec<COV_DIM>(const EVec<COV_DIM>&, const EVec<COV_DIM>&)>& residual_func,
495 const EMat<COV_DIM, COV_DIM>& sqrt_R
496) {
497 /*
498 * New mean is usually just the sum of the sigmas * weights:
499 *
500 * L+1
501 * x̂ = Σ Wᵢ⁽ᵐ⁾𝒳ᵢ
502 * i=0
503 *
504 * equations (19) and (23) in the paper show this,
505 * but we allow a custom function, usually for angle wrapping
506 */
507 EVec<COV_DIM> x = mean_func(sigmas, Wm);
508
509 /*
510 * Form an intermediate matrix S⁻ as:
511 *
512 * [√{W₁⁽ᶜ⁾}(𝒳_{1:L+1} - x̂) √{Rᵛ}]
513 *
514 * the part of equations (20) and (24) within the "qr{}"
515 */
516 EMat<COV_DIM, NUM_SIGMAS - 1 + COV_DIM> S_bar;
517 for (int i = 0; i < NUM_SIGMAS - 1; i++) {
518 S_bar.template block<COV_DIM, 1>(0, i) =
519 std::sqrt(Wc[1]) * residual_func(sigmas.template block<COV_DIM, 1>(0, i + 1), x);
520 }
521 S_bar.template block<COV_DIM, COV_DIM>(0, NUM_SIGMAS - 1) = sqrt_R;
522
523 /*
524 * Compute the square-root covariance of the sigma points.
525 *
526 * We transpose S⁻ first because we formed it by horizontally
527 * concatenating each part; it should be vertical so we can take
528 * the QR decomposition as defined in the "QR Decomposition" passage
529 * of section 3. "EFFICIENT SQUARE-ROOT IMPLEMENTATION"
530 *
531 * The resulting matrix R is the square-root covariance S, but it
532 * is upper triangular, so we need to transpose it.
533 *
534 * equations (20) and (24)
535 */
536 EMat<COV_DIM, COV_DIM> S = S_bar.transpose()
537 .householderQr()
538 .matrixQR()
539 .template block<COV_DIM, COV_DIM>(0, 0)
540 .template triangularView<Eigen::Upper>()
541 .transpose();
542 /*
543 * Update or downdate the square-root covariance with (𝒳₀-x̂)
544 * depending on whether its weight (W₀⁽ᶜ⁾) is positive or negative.
545 *
546 * equations (21) and (25)
547 */
548 Eigen::internal::llt_inplace<double, Eigen::Lower>::rankUpdate(
549 S, residual_func(sigmas.template block<COV_DIM, 1>(0, 0), x), Wc[0]
550 );
551
552 return std::make_tuple(x, S);
553}
554
555/**
556 * Generates sigma points and weights according to the paper [1]
557 * This is very different from Wan and Merwe's formulation.
558 *
559 * This only requires N + 2 sigma points instead of 2N + 1 sigma points.
560 * Rather than generating sigma points symmetrically around the mean, it
561 * generates them as vertices of an N-simplex.
562 *
563 * The performance of the filter using this reduced set of sigma points is
564 * identical to the standard method, so there is no downside to using it here.
565 *
566 * [1] A Scaled Spherical Simplex Filter (S3F) with a decreased n + 2 sigma
567 * points set size and equivalent 2n + 1 Unscented Kalman Filter (UKF)
568 * accuracy
569 *
570 * @tparam STATES the dimension of the state. NUM_SIGMAS sigma points and
571 * weights will be generated.
572 */
573template <int STATES>
575 public:
576 static constexpr int NUM_SIGMAS = STATES + 2;
577
578 /**
579 * Constructs a sigma point generator for Spherical Simplex sigma points
580 *
581 * @param alpha Determines the spread of the sigma points around the mean.
582 * Smaller values are closer to the mean, this is usually a small value.
583 * @param beta Incorporates prior knowledge of the distribution of the state.
584 * For Gaussian distributions, beta = 2 is optimal.
585 */
586 ScaledSphericalSimplexSigmaPoints(double alpha = 0.001, double beta = 2) {
587 compute_weights(alpha, beta);
588 }
589
590 /// Returns the number of sigma points, for simplex sigma points this is N+2.
591 int num_sigmas() { return NUM_SIGMAS; }
592
593 /**
594 * Computes the sigma points given a mean (x) and square-root covariance (S).
595 *
596 * @param x Vector of the means.
597 * @param S Square-root covariance.
598 *
599 * @return Matrix containing the sigma points. Each column contains one sigma
600 * point in the same space as x. The first column is the same as the mean,
601 * with the others arranged around the mean.
602 */
603 EMat<STATES, NUM_SIGMAS> square_root_sigma_points(
604 const EVec<STATES>& x, const EMat<STATES, STATES>& S
605 ) {
606 EMat<STATES, NUM_SIGMAS> sigmas = S * C_;
607 sigmas.colwise() += x;
608
609 return sigmas;
610 }
611
612 /// Returns a vector containing the weights of each sigma point for the mean.
613 const EVec<NUM_SIGMAS>& Wm() const { return Wm_; }
614
615 /// Returns a vector containing the weights of each sigma point for the covariance.
616 const EVec<NUM_SIGMAS>& Wc() const { return Wc_; }
617
618 /**
619 * Returns the weight for the i-th sigma point for the mean.
620 *
621 * @param i Element of the weights vector to return.
622 */
623 double Wm(int i) const { return Wm_(i); }
624
625 /**
626 * Returns the weight for the i-th sigma point for the covariance.
627 *
628 * @param i Element of the weights vector to return.
629 */
630 double Wc(int i) const { return Wc_(i); }
631
632 private:
633 EVec<NUM_SIGMAS> Wm_;
634 EVec<NUM_SIGMAS> Wc_;
635 EVec<STATES> q_;
636
637 EMat<STATES, NUM_SIGMAS> C_;
638
639 /**
640 * Computes the weights for the sigma points.
641 *
642 * @param alpha Determines the spread of the sigma points around the mean.
643 * Smaller values are closer to the mean, this is usually a small value.
644 * @param beta Incorporates prior knowledge of the distribution of the state.
645 * For Gaussian distributions, beta = 2 is optimal.
646 */
647 void compute_weights(double alpha, double beta) {
648 double c = 1 / (alpha * alpha * (STATES + 1));
649 Wm_ = EVec<NUM_SIGMAS>::Constant(c);
650 Wc_ = EVec<NUM_SIGMAS>::Constant(c);
651
652 Wm_(0) = 1 - (1 / (alpha * alpha));
653 Wc_(0) = 1 - (1 / (alpha * alpha)) + (1 - alpha * alpha + beta);
654
655 EVec<STATES> t;
656 for (int i = 0; i < STATES; i++) {
657 t(i) = i + 1;
658 }
659
660 EVec<STATES> q =
661 alpha * ((t * (STATES + 1)).cwiseQuotient(t + EVec<STATES>::Ones())).cwiseSqrt();
662
663 C_ = EMat<STATES, NUM_SIGMAS>::Zero();
664 for (int row = 0; row < STATES; row++) {
665 C_.row(row).segment(1, row + 1).setConstant(-q(row) / (row + 1));
666 }
667 C_.diagonal(2) = q;
668 }
669};
670
671// allow using both names
672template <int STATES, int INPUTS, int OUTPUTS>
674template <int STATES, int INPUTS, int OUTPUTS>
676template <int STATES, int INPUTS, int OUTPUTS>
677using SquareRootUnscentedKalmanFilter = UnscentedKalmanFilter<STATES, INPUTS, OUTPUTS>;
Definition unscented_kalman_filter.h:574
const EVec< NUM_SIGMAS > & Wm() const
Returns a vector containing the weights of each sigma point for the mean.
Definition unscented_kalman_filter.h:613
double Wc(int i) const
Definition unscented_kalman_filter.h:630
int num_sigmas()
Returns the number of sigma points, for simplex sigma points this is N+2.
Definition unscented_kalman_filter.h:591
const EVec< NUM_SIGMAS > & Wc() const
Returns a vector containing the weights of each sigma point for the covariance.
Definition unscented_kalman_filter.h:616
ScaledSphericalSimplexSigmaPoints(double alpha=0.001, double beta=2)
Definition unscented_kalman_filter.h:586
EMat< STATES, NUM_SIGMAS > square_root_sigma_points(const EVec< STATES > &x, const EMat< STATES, STATES > &S)
Definition unscented_kalman_filter.h:603
double Wm(int i) const
Definition unscented_kalman_filter.h:623
Definition unscented_kalman_filter.h:64
StateMatrix P() const
Returns the reconstructed covariance matrix P.
Definition unscented_kalman_filter.h:185
UnscentedKalmanFilter(const std::function< StateVector(const StateVector &, const InputVector &)> &f, const std::function< OutputVector(const StateVector &, const InputVector &)> &h, const WithInputIntegrator &integrator, const StateVector &state_stddevs, const OutputVector &measurement_stddevs, const std::function< StateVector(const EMat< STATES, NUM_SIGMAS > &, const EVec< NUM_SIGMAS > &)> &mean_func_X, const std::function< OutputVector(const EMat< OUTPUTS, NUM_SIGMAS > &, const EVec< NUM_SIGMAS > &)> &mean_func_Y, const std::function< StateVector(const StateVector &, const StateVector &)> &residual_func_X, const std::function< OutputVector(const OutputVector &, const OutputVector &)> &residual_func_Y, const std::function< StateVector(const StateVector &, const StateVector &)> &add_func_X)
Definition unscented_kalman_filter.h:139
void set_S(const StateMatrix &S)
Definition unscented_kalman_filter.h:182
void set_xhat(int i, double value)
Definition unscented_kalman_filter.h:212
void set_P(const StateMatrix &P)
Definition unscented_kalman_filter.h:192
StateVector xhat() const
Returns the current state estimate x-hat.
Definition unscented_kalman_filter.h:195
void correct(const InputVector &u, const EVec< ROWS > &y, const std::function< EVec< ROWS >(const StateVector &, const InputVector &)> &h, const EVec< ROWS > &measurement_stddevs)
Definition unscented_kalman_filter.h:309
UnscentedKalmanFilter(const std::function< StateVector(const StateVector &, const InputVector &)> &f, const std::function< OutputVector(const StateVector &, const InputVector &)> &h, const WithInputIntegrator &integrator, const StateVector &state_stddevs, const OutputVector &measurement_stddevs)
Definition unscented_kalman_filter.h:91
StateMatrix S() const
Returns the square-root covariance matrix S.
Definition unscented_kalman_filter.h:167
double xhat(int i) const
Definition unscented_kalman_filter.h:202
void predict(const InputVector &u, double dt)
Definition unscented_kalman_filter.h:227
void reset()
Resets the filter.
Definition unscented_kalman_filter.h:215
void set_xhat(const StateVector &xhat)
Set the current state estimate x-hat.
Definition unscented_kalman_filter.h:205
void correct(const InputVector &u, const EVec< ROWS > &y, const std::function< EVec< ROWS >(const StateVector &, const InputVector &)> &h, const EVec< ROWS > measurement_stddevs, const std::function< EVec< ROWS >(const EMat< ROWS, NUM_SIGMAS > &, const EVec< NUM_SIGMAS > &)> &mean_func_Y, const std::function< EVec< ROWS >(const EVec< ROWS > &, const EVec< ROWS > &)> &residual_func_Y, const std::function< StateVector(const StateVector &, const StateVector &)> &residual_func_X, const std::function< StateVector(const StateVector &, const StateVector &)> &add_func_X)
Definition unscented_kalman_filter.h:352
double S(int i, int j) const
Definition unscented_kalman_filter.h:175
void correct(const InputVector &u, const OutputVector &y, const EVec< OUTPUTS > &measurement_stddevs)
Definition unscented_kalman_filter.h:288
void correct(const InputVector &u, const OutputVector &y)
Definition unscented_kalman_filter.h:272