ukf.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014, 2015, 2016, Charles River Analytics, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided
14  * with the distribution.
15  * 3. Neither the name of the copyright holder nor the names of its
16  * contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "robot_localization/ukf.h"
35 
36 #include <XmlRpcException.h>
37 
38 #include <sstream>
39 #include <iomanip>
40 #include <limits>
41 
42 #include <Eigen/Cholesky>
43 
44 #include <iostream>
45 #include <vector>
46 
47 #include <assert.h>
48 
49 namespace RobotLocalization
50 {
51  Ukf::Ukf(std::vector<double> args) :
52  FilterBase(), // Must initialize filter base!
53  uncorrected_(true)
54  {
55  assert(args.size() == 3);
56 
57  double alpha = args[0];
58  double kappa = args[1];
59  double beta = args[2];
60 
61  size_t sigmaCount = (STATE_SIZE << 1) + 1;
62  sigmaPoints_.resize(sigmaCount, Eigen::VectorXd(STATE_SIZE));
63 
64  // Prepare constants
65  lambda_ = alpha * alpha * (STATE_SIZE + kappa) - STATE_SIZE;
66 
67  stateWeights_.resize(sigmaCount);
68  covarWeights_.resize(sigmaCount);
69 
71  covarWeights_[0] = stateWeights_[0] + (1 - (alpha * alpha) + beta);
72  sigmaPoints_[0].setZero();
73  for (size_t i = 1; i < sigmaCount; ++i)
74  {
75  sigmaPoints_[i].setZero();
76  stateWeights_[i] = 1 / (2 * (STATE_SIZE + lambda_));
78  }
79  }
80 
82  {
83  }
84 
85  void Ukf::correct(const Measurement &measurement)
86  {
87  FB_DEBUG("---------------------- Ukf::correct ----------------------\n" <<
88  "State is:\n" << state_ <<
89  "\nMeasurement is:\n" << measurement.measurement_ <<
90  "\nMeasurement covariance is:\n" << measurement.covariance_ << "\n");
91 
92  // In our implementation, it may be that after we call predict once, we call correct
93  // several times in succession (multiple measurements with different time stamps). In
94  // that event, the sigma points need to be updated to reflect the current state.
95  // Throughout prediction and correction, we attempt to maximize efficiency in Eigen.
96  if (!uncorrected_)
97  {
98  // Take the square root of a small fraction of the estimateErrorCovariance_ using LL' decomposition
100 
101  // Compute sigma points
102 
103  // First sigma point is the current state
104  sigmaPoints_[0] = state_;
105 
106  // Next STATE_SIZE sigma points are state + weightedCovarSqrt_[ith column]
107  // STATE_SIZE sigma points after that are state - weightedCovarSqrt_[ith column]
108  for (size_t sigmaInd = 0; sigmaInd < STATE_SIZE; ++sigmaInd)
109  {
110  sigmaPoints_[sigmaInd + 1] = state_ + weightedCovarSqrt_.col(sigmaInd);
111  sigmaPoints_[sigmaInd + 1 + STATE_SIZE] = state_ - weightedCovarSqrt_.col(sigmaInd);
112  }
113  }
114 
115  // We don't want to update everything, so we need to build matrices that only update
116  // the measured parts of our state vector
117 
118  // First, determine how many state vector values we're updating
119  std::vector<size_t> updateIndices;
120  for (size_t i = 0; i < measurement.updateVector_.size(); ++i)
121  {
122  if (measurement.updateVector_[i])
123  {
124  // Handle nan and inf values in measurements
125  if (std::isnan(measurement.measurement_(i)))
126  {
127  FB_DEBUG("Value at index " << i << " was nan. Excluding from update.\n");
128  }
129  else if (std::isinf(measurement.measurement_(i)))
130  {
131  FB_DEBUG("Value at index " << i << " was inf. Excluding from update.\n");
132  }
133  else
134  {
135  updateIndices.push_back(i);
136  }
137  }
138  }
139 
140  FB_DEBUG("Update indices are:\n" << updateIndices << "\n");
141 
142  size_t updateSize = updateIndices.size();
143 
144  // Now set up the relevant matrices
145  Eigen::VectorXd stateSubset(updateSize); // x (in most literature)
146  Eigen::VectorXd measurementSubset(updateSize); // z
147  Eigen::MatrixXd measurementCovarianceSubset(updateSize, updateSize); // R
148  Eigen::MatrixXd stateToMeasurementSubset(updateSize, STATE_SIZE); // H
149  Eigen::MatrixXd kalmanGainSubset(STATE_SIZE, updateSize); // K
150  Eigen::VectorXd innovationSubset(updateSize); // z - Hx
151  Eigen::VectorXd predictedMeasurement(updateSize);
152  Eigen::VectorXd sigmaDiff(updateSize);
153  Eigen::MatrixXd predictedMeasCovar(updateSize, updateSize);
154  Eigen::MatrixXd crossCovar(STATE_SIZE, updateSize);
155 
156  std::vector<Eigen::VectorXd> sigmaPointMeasurements(sigmaPoints_.size(), Eigen::VectorXd(updateSize));
157 
158  stateSubset.setZero();
159  measurementSubset.setZero();
160  measurementCovarianceSubset.setZero();
161  stateToMeasurementSubset.setZero();
162  kalmanGainSubset.setZero();
163  innovationSubset.setZero();
164  predictedMeasurement.setZero();
165  predictedMeasCovar.setZero();
166  crossCovar.setZero();
167 
168  // Now build the sub-matrices from the full-sized matrices
169  for (size_t i = 0; i < updateSize; ++i)
170  {
171  measurementSubset(i) = measurement.measurement_(updateIndices[i]);
172  stateSubset(i) = state_(updateIndices[i]);
173 
174  for (size_t j = 0; j < updateSize; ++j)
175  {
176  measurementCovarianceSubset(i, j) = measurement.covariance_(updateIndices[i], updateIndices[j]);
177  }
178 
179  // Handle negative (read: bad) covariances in the measurement. Rather
180  // than exclude the measurement or make up a covariance, just take
181  // the absolute value.
182  if (measurementCovarianceSubset(i, i) < 0)
183  {
184  FB_DEBUG("WARNING: Negative covariance for index " << i <<
185  " of measurement (value is" << measurementCovarianceSubset(i, i) <<
186  "). Using absolute value...\n");
187 
188  measurementCovarianceSubset(i, i) = ::fabs(measurementCovarianceSubset(i, i));
189  }
190 
191  // If the measurement variance for a given variable is very
192  // near 0 (as in e-50 or so) and the variance for that
193  // variable in the covariance matrix is also near zero, then
194  // the Kalman gain computation will blow up. Really, no
195  // measurement can be completely without error, so add a small
196  // amount in that case.
197  if (measurementCovarianceSubset(i, i) < 1e-9)
198  {
199  measurementCovarianceSubset(i, i) = 1e-9;
200 
201  FB_DEBUG("WARNING: measurement had very small error covariance for index " <<
202  updateIndices[i] <<
203  ". Adding some noise to maintain filter stability.\n");
204  }
205  }
206 
207  // The state-to-measurement function, h, will now be a measurement_size x full_state_size
208  // matrix, with ones in the (i, i) locations of the values to be updated
209  for (size_t i = 0; i < updateSize; ++i)
210  {
211  stateToMeasurementSubset(i, updateIndices[i]) = 1;
212  }
213 
214  FB_DEBUG("Current state subset is:\n" << stateSubset <<
215  "\nMeasurement subset is:\n" << measurementSubset <<
216  "\nMeasurement covariance subset is:\n" << measurementCovarianceSubset <<
217  "\nState-to-measurement subset is:\n" << stateToMeasurementSubset << "\n");
218 
219  // (1) Generate sigma points, use them to generate a predicted measurement
220  for (size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
221  {
222  sigmaPointMeasurements[sigmaInd] = stateToMeasurementSubset * sigmaPoints_[sigmaInd];
223  predictedMeasurement.noalias() += stateWeights_[sigmaInd] * sigmaPointMeasurements[sigmaInd];
224  }
225 
226  // (2) Use the sigma point measurements and predicted measurement to compute a predicted
227  // measurement covariance matrix P_zz and a state/measurement cross-covariance matrix P_xz.
228  for (size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
229  {
230  sigmaDiff = sigmaPointMeasurements[sigmaInd] - predictedMeasurement;
231  predictedMeasCovar.noalias() += covarWeights_[sigmaInd] * (sigmaDiff * sigmaDiff.transpose());
232  crossCovar.noalias() += covarWeights_[sigmaInd] * ((sigmaPoints_[sigmaInd] - state_) * sigmaDiff.transpose());
233  }
234 
235  // (3) Compute the Kalman gain, making sure to use the actual measurement covariance: K = P_xz * (P_zz + R)^-1
236  Eigen::MatrixXd invInnovCov = (predictedMeasCovar + measurementCovarianceSubset).inverse();
237  kalmanGainSubset = crossCovar * invInnovCov;
238 
239  // (4) Apply the gain to the difference between the actual and predicted measurements: x = x + K(z - z_hat)
240  innovationSubset = (measurementSubset - predictedMeasurement);
241 
242  // Wrap angles in the innovation
243  for (size_t i = 0; i < updateSize; ++i)
244  {
245  if (updateIndices[i] == StateMemberRoll ||
246  updateIndices[i] == StateMemberPitch ||
247  updateIndices[i] == StateMemberYaw)
248  {
249  while (innovationSubset(i) < -PI)
250  {
251  innovationSubset(i) += TAU;
252  }
253 
254  while (innovationSubset(i) > PI)
255  {
256  innovationSubset(i) -= TAU;
257  }
258  }
259  }
260 
261  // (5) Check Mahalanobis distance of innovation
262  if (checkMahalanobisThreshold(innovationSubset, invInnovCov, measurement.mahalanobisThresh_))
263  {
264  state_.noalias() += kalmanGainSubset * innovationSubset;
265 
266  // (6) Compute the new estimate error covariance P = P - (K * P_zz * K')
267  estimateErrorCovariance_.noalias() -= (kalmanGainSubset * predictedMeasCovar * kalmanGainSubset.transpose());
268 
269  wrapStateAngles();
270 
271  // Mark that we need to re-compute sigma points for successive corrections
272  uncorrected_ = false;
273 
274  FB_DEBUG("Predicated measurement covariance is:\n" << predictedMeasCovar <<
275  "\nCross covariance is:\n" << crossCovar <<
276  "\nKalman gain subset is:\n" << kalmanGainSubset <<
277  "\nInnovation:\n" << innovationSubset <<
278  "\nCorrected full state is:\n" << state_ <<
279  "\nCorrected full estimate error covariance is:\n" << estimateErrorCovariance_ <<
280  "\n\n---------------------- /Ukf::correct ----------------------\n");
281  }
282  }
283 
284  void Ukf::predict(const double referenceTime, const double delta)
285  {
286  FB_DEBUG("---------------------- Ukf::predict ----------------------\n" <<
287  "delta is " << delta <<
288  "\nstate is " << state_ << "\n");
289 
290  double roll = state_(StateMemberRoll);
291  double pitch = state_(StateMemberPitch);
292  double yaw = state_(StateMemberYaw);
293 
294  // We'll need these trig calculations a lot.
295  double sp = ::sin(pitch);
296  double cp = ::cos(pitch);
297  double cpi = 1.0 / cp;
298  double tp = sp * cpi;
299 
300  double sr = ::sin(roll);
301  double cr = ::cos(roll);
302 
303  double sy = ::sin(yaw);
304  double cy = ::cos(yaw);
305 
306  prepareControl(referenceTime, delta);
307 
308  // Prepare the transfer function
309  transferFunction_(StateMemberX, StateMemberVx) = cy * cp * delta;
310  transferFunction_(StateMemberX, StateMemberVy) = (cy * sp * sr - sy * cr) * delta;
311  transferFunction_(StateMemberX, StateMemberVz) = (cy * sp * cr + sy * sr) * delta;
315  transferFunction_(StateMemberY, StateMemberVx) = sy * cp * delta;
316  transferFunction_(StateMemberY, StateMemberVy) = (sy * sp * sr + cy * cr) * delta;
317  transferFunction_(StateMemberY, StateMemberVz) = (sy * sp * cr - cy * sr) * delta;
322  transferFunction_(StateMemberZ, StateMemberVy) = cp * sr * delta;
323  transferFunction_(StateMemberZ, StateMemberVz) = cp * cr * delta;
333  transferFunction_(StateMemberYaw, StateMemberVyaw) = cr * cpi * delta;
337 
338  // (1) Take the square root of a small fraction of the estimateErrorCovariance_ using LL' decomposition
340 
341  // (2) Compute sigma points *and* pass them through the transfer function to save
342  // the extra loop
343 
344  // First sigma point is the current state
346 
347  // Next STATE_SIZE sigma points are state + weightedCovarSqrt_[ith column]
348  // STATE_SIZE sigma points after that are state - weightedCovarSqrt_[ith column]
349  for (size_t sigmaInd = 0; sigmaInd < STATE_SIZE; ++sigmaInd)
350  {
351  sigmaPoints_[sigmaInd + 1] = transferFunction_ * (state_ + weightedCovarSqrt_.col(sigmaInd));
352  sigmaPoints_[sigmaInd + 1 + STATE_SIZE] = transferFunction_ * (state_ - weightedCovarSqrt_.col(sigmaInd));
353  }
354 
355  // (3) Sum the weighted sigma points to generate a new state prediction
356  state_.setZero();
357  for (size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
358  {
359  state_.noalias() += stateWeights_[sigmaInd] * sigmaPoints_[sigmaInd];
360  }
361 
362  // (4) Now us the sigma points and the predicted state to compute a predicted covariance
363  estimateErrorCovariance_.setZero();
364  Eigen::VectorXd sigmaDiff(STATE_SIZE);
365  for (size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
366  {
367  sigmaDiff = (sigmaPoints_[sigmaInd] - state_);
368  estimateErrorCovariance_.noalias() += covarWeights_[sigmaInd] * (sigmaDiff * sigmaDiff.transpose());
369  }
370 
371  // (5) Not strictly in the theoretical UKF formulation, but necessary here
372  // to ensure that we actually incorporate the processNoiseCovariance_
373  Eigen::MatrixXd *processNoiseCovariance = &processNoiseCovariance_;
374 
376  {
378  processNoiseCovariance = &dynamicProcessNoiseCovariance_;
379  }
380 
381  estimateErrorCovariance_.noalias() += delta * (*processNoiseCovariance);
382 
383  // Keep the angles bounded
384  wrapStateAngles();
385 
386  // Mark that we can keep these sigma points
387  uncorrected_ = true;
388 
389  FB_DEBUG("Predicted state is:\n" << state_ <<
390  "\nPredicted estimate error covariance is:\n" << estimateErrorCovariance_ <<
391  "\n\n--------------------- /Ukf::predict ----------------------\n");
392  }
393 
394 } // namespace RobotLocalization
std::vector< Eigen::VectorXd > sigmaPoints_
The UKF sigma points.
Definition: ukf.h:96
Eigen::MatrixXd weightedCovarSqrt_
This matrix is used to generate the sigmaPoints_.
Definition: ukf.h:100
const int STATE_SIZE
Global constants that define our state vector size and offsets to groups of values within that state...
Definition: filter_common.h:75
Eigen::MatrixXd estimateErrorCovariance_
This matrix stores the total error in our position estimate (the state_ variable).
Definition: filter_base.h:447
void correct(const Measurement &measurement)
Carries out the correct step in the predict/update cycle.
Definition: ukf.cpp:85
Structure used for storing and comparing measurements (for priority queues)
Definition: filter_base.h:61
Eigen::MatrixXd transferFunction_
The Kalman filter transfer function.
Definition: filter_base.h:509
std::vector< double > covarWeights_
The weights associated with each sigma point when calculating a predicted estimateErrorCovariance_.
Definition: ukf.h:110
~Ukf()
Destructor for the Ukf class.
Definition: ukf.cpp:81
#define FB_DEBUG(msg)
void prepareControl(const double referenceTime, const double predictionDelta)
Converts the control term to an acceleration to be applied in the prediction step.
TF2SIMD_FORCE_INLINE Quaternion inverse(const Quaternion &q)
Eigen::MatrixXd dynamicProcessNoiseCovariance_
Gets updated when useDynamicProcessNoise_ is true.
Definition: filter_base.h:442
virtual void wrapStateAngles()
Keeps the state Euler angles in the range [-pi, pi].
const double TAU
Definition: filter_common.h:92
void predict(const double referenceTime, const double delta)
Carries out the predict step in the predict/update cycle.
Definition: ukf.cpp:284
virtual bool checkMahalanobisThreshold(const Eigen::VectorXd &innovation, const Eigen::MatrixXd &invCovariance, const double nsigmas)
Tests if innovation is within N-sigmas of covariance. Returns true if passed the test.
Ukf(std::vector< double > args)
Constructor for the Ukf class.
Definition: ukf.cpp:51
bool uncorrected_
Used to determine if we need to re-compute the sigma points when carrying out multiple corrections...
Definition: ukf.h:119
bool useDynamicProcessNoiseCovariance_
If true, uses the robot&#39;s vehicle state and the static process noise covariance matrix to generate a ...
Definition: filter_base.h:528
std::vector< double > stateWeights_
The weights associated with each sigma point when generating a new state.
Definition: ukf.h:105
Eigen::VectorXd measurement_
Definition: filter_base.h:69
void computeDynamicProcessNoiseCovariance(const Eigen::VectorXd &state, const double delta)
Computes a dynamic process noise covariance matrix using the parameterized state. ...
Eigen::VectorXd state_
This is the robot&#39;s state vector, which is what we are trying to filter. The values in this vector ar...
Definition: filter_base.h:494
double lambda_
Used in weight generation for the sigma points.
Definition: ukf.h:114
Eigen::MatrixXd processNoiseCovariance_
As we move through the world, we follow a predict/update cycle. If one were to imagine a scenario whe...
Definition: filter_base.h:482
std::vector< int > updateVector_
Definition: filter_base.h:75
const double PI
Common variables.
Definition: filter_common.h:91


robot_localization
Author(s): Tom Moore
autogenerated on Wed Feb 3 2021 03:34:02