ukf.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2015, Charles River Analytics, Inc.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * 1. Redistributions of source code must retain the above copyright
00010  * notice, this list of conditions and the following disclaimer.
00011  * 2. Redistributions in binary form must reproduce the above
00012  * copyright notice, this list of conditions and the following
00013  * disclaimer in the documentation and/or other materials provided
00014  * with the distribution.
00015  * 3. Neither the name of the copyright holder nor the names of its
00016  * contributors may be used to endorse or promote products derived
00017  * from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00020  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00021  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00022  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00023  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00024  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00025  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00026  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00027  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00028  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00029  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00030  * POSSIBILITY OF SUCH DAMAGE.
00031  */
00032 
00033 #include "robot_localization/ukf.h"
00034 #include "robot_localization/filter_common.h"
00035 
00036 #include <XmlRpcException.h>
00037 
00038 #include <sstream>
00039 #include <iomanip>
00040 #include <limits>
00041 
00042 #include <Eigen/Cholesky>
00043 
00044 #include <iostream>
00045 
00046 #include <assert.h>
00047 
00048 namespace RobotLocalization
00049 {
00050   Ukf::Ukf(std::vector<double> args) :
00051     FilterBase(), // Must initialize filter base!
00052     uncorrected_(true)
00053   {
00054     assert(args.size() == 3);
00055 
00056     double alpha = args[0];
00057     double kappa = args[1];
00058     double beta = args[2];
00059 
00060     size_t sigmaCount = (STATE_SIZE << 1) + 1;
00061     sigmaPoints_.resize(sigmaCount, Eigen::VectorXd(STATE_SIZE));
00062 
00063     // Prepare constants
00064     lambda_ = alpha * alpha * (STATE_SIZE + kappa) - STATE_SIZE;
00065 
00066     stateWeights_.resize(sigmaCount);
00067     covarWeights_.resize(sigmaCount);
00068 
00069     stateWeights_[0] = lambda_ / (STATE_SIZE + lambda_);
00070     covarWeights_[0] = stateWeights_[0] + (1 - (alpha * alpha) + beta);
00071     sigmaPoints_[0].setZero();
00072     for(size_t i = 1; i < sigmaCount; ++i)
00073     {
00074       sigmaPoints_[i].setZero();
00075       stateWeights_[i] =  1 / (2 * (STATE_SIZE + lambda_));
00076       covarWeights_[i] = stateWeights_[i];
00077     }
00078   }
00079 
00080   Ukf::~Ukf()
00081   {
00082   }
00083 
00084   void Ukf::correct(const Measurement &measurement)
00085   {
00086     FB_DEBUG("---------------------- Ukf::correct ----------------------\n" <<
00087              "State is:\n" << state_ <<
00088              "\nMeasurement is:\n" << measurement.measurement_ <<
00089              "\nMeasurement covariance is:\n" << measurement.covariance_ << "\n");
00090 
00091     // In our implementation, it may be that after we call predict once, we call correct
00092     // several times in succession (multiple measurements with different time stamps). In
00093     // that event, the sigma points need to be updated to reflect the current state.
00094     // Throughout prediction and correction, we attempt to maximize efficiency in Eigen.
00095     if(!uncorrected_)
00096     {
00097       // Take the square root of a small fraction of the estimateErrorCovariance_ using LL' decomposition
00098       weightedCovarSqrt_ = ((STATE_SIZE + lambda_) * estimateErrorCovariance_).llt().matrixL();
00099 
00100       // Compute sigma points
00101 
00102       // First sigma point is the current state
00103       sigmaPoints_[0] = state_;
00104 
00105       // Next STATE_SIZE sigma points are state + weightedCovarSqrt_[ith column]
00106       // STATE_SIZE sigma points after that are state - weightedCovarSqrt_[ith column]
00107       for(size_t sigmaInd = 0; sigmaInd < STATE_SIZE; ++sigmaInd)
00108       {
00109         sigmaPoints_[sigmaInd + 1] = state_ + weightedCovarSqrt_.col(sigmaInd);
00110         sigmaPoints_[sigmaInd + 1 + STATE_SIZE] = state_ - weightedCovarSqrt_.col(sigmaInd);
00111       }
00112     }
00113 
00114     // We don't want to update everything, so we need to build matrices that only update
00115     // the measured parts of our state vector
00116 
00117     // First, determine how many state vector values we're updating
00118     std::vector<size_t> updateIndices;
00119     for (size_t i = 0; i < measurement.updateVector_.size(); ++i)
00120     {
00121       if (measurement.updateVector_[i])
00122       {
00123         // Handle nan and inf values in measurements
00124         if (std::isnan(measurement.measurement_(i)))
00125         {
00126           FB_DEBUG("Value at index " << i << " was nan. Excluding from update.\n");
00127         }
00128         else if (std::isinf(measurement.measurement_(i)))
00129         {
00130           FB_DEBUG("Value at index " << i << " was inf. Excluding from update.\n");
00131         }
00132         else
00133         {
00134           updateIndices.push_back(i);
00135         }
00136       }
00137     }
00138 
00139     FB_DEBUG("Update indices are:\n" << updateIndices << "\n");
00140 
00141     size_t updateSize = updateIndices.size();
00142 
00143     // Now set up the relevant matrices
00144     Eigen::VectorXd stateSubset(updateSize);                             // x (in most literature)
00145     Eigen::VectorXd measurementSubset(updateSize);                       // z
00146     Eigen::MatrixXd measurementCovarianceSubset(updateSize, updateSize); // R
00147     Eigen::MatrixXd stateToMeasurementSubset(updateSize, STATE_SIZE);    // H
00148     Eigen::MatrixXd kalmanGainSubset(STATE_SIZE, updateSize);            // K
00149     Eigen::VectorXd innovationSubset(updateSize);                        // z - Hx
00150     Eigen::VectorXd predictedMeasurement(updateSize);
00151     Eigen::VectorXd sigmaDiff(updateSize);
00152     Eigen::MatrixXd predictedMeasCovar(updateSize, updateSize);
00153     Eigen::MatrixXd crossCovar(STATE_SIZE, updateSize);
00154 
00155     std::vector<Eigen::VectorXd> sigmaPointMeasurements(sigmaPoints_.size(), Eigen::VectorXd(updateSize));
00156 
00157     stateSubset.setZero();
00158     measurementSubset.setZero();
00159     measurementCovarianceSubset.setZero();
00160     stateToMeasurementSubset.setZero();
00161     kalmanGainSubset.setZero();
00162     innovationSubset.setZero();
00163     predictedMeasurement.setZero();
00164     predictedMeasCovar.setZero();
00165     crossCovar.setZero();
00166 
00167     // Now build the sub-matrices from the full-sized matrices
00168     for (size_t i = 0; i < updateSize; ++i)
00169     {
00170       measurementSubset(i) = measurement.measurement_(updateIndices[i]);
00171       stateSubset(i) = state_(updateIndices[i]);
00172 
00173       for (size_t j = 0; j < updateSize; ++j)
00174       {
00175         measurementCovarianceSubset(i, j) = measurement.covariance_(updateIndices[i], updateIndices[j]);
00176       }
00177 
00178       // Handle negative (read: bad) covariances in the measurement. Rather
00179       // than exclude the measurement or make up a covariance, just take
00180       // the absolute value.
00181       if (measurementCovarianceSubset(i, i) < 0)
00182       {
00183         FB_DEBUG("WARNING: Negative covariance for index " << i <<
00184                  " of measurement (value is" << measurementCovarianceSubset(i, i) <<
00185                  "). Using absolute value...\n");
00186 
00187         measurementCovarianceSubset(i, i) = ::fabs(measurementCovarianceSubset(i, i));
00188       }
00189 
00190       // If the measurement variance for a given variable is very
00191       // near 0 (as in e-50 or so) and the variance for that
00192       // variable in the covariance matrix is also near zero, then
00193       // the Kalman gain computation will blow up. Really, no
00194       // measurement can be completely without error, so add a small
00195       // amount in that case.
00196       if (measurementCovarianceSubset(i, i) < 1e-9)
00197       {
00198         measurementCovarianceSubset(i, i) = 1e-9;
00199 
00200         FB_DEBUG("WARNING: measurement had very small error covariance for index " <<
00201                  updateIndices[i] <<
00202                  ". Adding some noise to maintain filter stability.\n");
00203       }
00204     }
00205 
00206     // The state-to-measurement function, h, will now be a measurement_size x full_state_size
00207     // matrix, with ones in the (i, i) locations of the values to be updated
00208     for (size_t i = 0; i < updateSize; ++i)
00209     {
00210       stateToMeasurementSubset(i, updateIndices[i]) = 1;
00211     }
00212 
00213     FB_DEBUG("Current state subset is:\n" << stateSubset <<
00214              "\nMeasurement subset is:\n" << measurementSubset <<
00215              "\nMeasurement covariance subset is:\n" << measurementCovarianceSubset <<
00216              "\nState-to-measurement subset is:\n" << stateToMeasurementSubset << "\n");
00217 
00218     // (1) Generate sigma points, use them to generate a predicted measurement
00219     for(size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
00220     {
00221       sigmaPointMeasurements[sigmaInd] = stateToMeasurementSubset * sigmaPoints_[sigmaInd];
00222       predictedMeasurement.noalias() += stateWeights_[sigmaInd] * sigmaPointMeasurements[sigmaInd];
00223     }
00224 
00225     // (2) Use the sigma point measurements and predicted measurement to compute a predicted
00226     // measurement covariance matrix P_zz and a state/measurement cross-covariance matrix P_xz.
00227     for(size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
00228     {
00229       sigmaDiff = sigmaPointMeasurements[sigmaInd] - predictedMeasurement;
00230       predictedMeasCovar.noalias() += covarWeights_[sigmaInd] * (sigmaDiff * sigmaDiff.transpose());
00231       crossCovar.noalias() += covarWeights_[sigmaInd] * ((sigmaPoints_[sigmaInd] - state_) * sigmaDiff.transpose());
00232     }
00233 
00234     // (3) Compute the Kalman gain, making sure to use the actual measurement covariance: K = P_xz * (P_zz + R)^-1
00235     Eigen::MatrixXd invInnovCov = (predictedMeasCovar + measurementCovarianceSubset).inverse();
00236     kalmanGainSubset = crossCovar * invInnovCov;
00237 
00238     // (4) Apply the gain to the difference between the actual and predicted measurements: x = x + K(z - z_hat)
00239     innovationSubset = (measurementSubset - predictedMeasurement);
00240 
00241     // (5) Check Mahalanobis distance of innovation
00242     if (checkMahalanobisThreshold(innovationSubset, invInnovCov, measurement.mahalanobisThresh_))
00243     {
00244       // Wrap angles in the innovation
00245       for (size_t i = 0; i < updateSize; ++i)
00246       {
00247         if (updateIndices[i] == StateMemberRoll || updateIndices[i] == StateMemberPitch || updateIndices[i] == StateMemberYaw)
00248         {
00249           while (innovationSubset(i) < -PI)
00250           {
00251             innovationSubset(i) += TAU;
00252           }
00253 
00254           while (innovationSubset(i) > PI)
00255           {
00256             innovationSubset(i) -= TAU;
00257           }
00258         }
00259       }
00260 
00261       state_.noalias() += kalmanGainSubset * innovationSubset;
00262 
00263       // (6) Compute the new estimate error covariance P = P - (K * P_zz * K')
00264       estimateErrorCovariance_.noalias() -= (kalmanGainSubset * predictedMeasCovar * kalmanGainSubset.transpose());
00265 
00266       wrapStateAngles();
00267 
00268       // Mark that we need to re-compute sigma points for successive corrections
00269       uncorrected_ = false;
00270 
00271       FB_DEBUG("Predicated measurement covariance is:\n" << predictedMeasCovar <<
00272                "\nCross covariance is:\n" << crossCovar <<
00273                "\nKalman gain subset is:\n" << kalmanGainSubset <<
00274                "\nInnovation:\n" << innovationSubset <<
00275                "\nCorrected full state is:\n" << state_ <<
00276                "\nCorrected full estimate error covariance is:\n" << estimateErrorCovariance_ <<
00277                "\n\n---------------------- /Ukf::correct ----------------------\n");
00278     }
00279   }
00280 
00281   void Ukf::predict(const double delta)
00282   {
00283     FB_DEBUG("---------------------- Ukf::predict ----------------------\n" <<
00284              "delta is " << delta <<
00285              "\nstate is " << state_ << "\n");
00286 
00287     double roll = state_(StateMemberRoll);
00288     double pitch = state_(StateMemberPitch);
00289     double yaw = state_(StateMemberYaw);
00290 
00291     // We'll need these trig calculations a lot.
00292     double cr = cos(roll);
00293     double cp = cos(pitch);
00294     double cy = cos(yaw);
00295     double sr = sin(roll);
00296     double sp = sin(pitch);
00297     double sy = sin(yaw);
00298 
00299     // Prepare the transfer function
00300     transferFunction_(StateMemberX, StateMemberVx) = cy * cp * delta;
00301     transferFunction_(StateMemberX, StateMemberVy) = (cy * sp * sr - sy * cr) * delta;
00302     transferFunction_(StateMemberX, StateMemberVz) = (cy * sp * cr + sy * sr) * delta;
00303     transferFunction_(StateMemberX, StateMemberAx) = 0.5 * transferFunction_(StateMemberX, StateMemberVx) * delta;
00304     transferFunction_(StateMemberX, StateMemberAy) = 0.5 * transferFunction_(StateMemberX, StateMemberVy) * delta;
00305     transferFunction_(StateMemberX, StateMemberAz) = 0.5 * transferFunction_(StateMemberX, StateMemberVz) * delta;
00306     transferFunction_(StateMemberY, StateMemberVx) = sy * cp * delta;
00307     transferFunction_(StateMemberY, StateMemberVy) = (sy * sp * sr + cy * cr) * delta;
00308     transferFunction_(StateMemberY, StateMemberVz) = (sy * sp * cr - cy * sr) * delta;
00309     transferFunction_(StateMemberY, StateMemberAx) = 0.5 * transferFunction_(StateMemberY, StateMemberVx) * delta;
00310     transferFunction_(StateMemberY, StateMemberAy) = 0.5 * transferFunction_(StateMemberY, StateMemberVy) * delta;
00311     transferFunction_(StateMemberY, StateMemberAz) = 0.5 * transferFunction_(StateMemberY, StateMemberVz) * delta;
00312     transferFunction_(StateMemberZ, StateMemberVx) = -sp * delta;
00313     transferFunction_(StateMemberZ, StateMemberVy) = cp * sr * delta;
00314     transferFunction_(StateMemberZ, StateMemberVz) = cp * cr * delta;
00315     transferFunction_(StateMemberZ, StateMemberAx) = 0.5 * transferFunction_(StateMemberZ, StateMemberVx) * delta;
00316     transferFunction_(StateMemberZ, StateMemberAy) = 0.5 * transferFunction_(StateMemberZ, StateMemberVy) * delta;
00317     transferFunction_(StateMemberZ, StateMemberAz) = 0.5 * transferFunction_(StateMemberZ, StateMemberVz) * delta;
00318     transferFunction_(StateMemberRoll, StateMemberVroll) = transferFunction_(StateMemberX, StateMemberVx);
00319     transferFunction_(StateMemberRoll, StateMemberVpitch) = transferFunction_(StateMemberX, StateMemberVy);
00320     transferFunction_(StateMemberRoll, StateMemberVyaw) = transferFunction_(StateMemberX, StateMemberVz);
00321     transferFunction_(StateMemberPitch, StateMemberVroll) = transferFunction_(StateMemberY, StateMemberVx);
00322     transferFunction_(StateMemberPitch, StateMemberVpitch) = transferFunction_(StateMemberY, StateMemberVy);
00323     transferFunction_(StateMemberPitch, StateMemberVyaw) = transferFunction_(StateMemberY, StateMemberVz);
00324     transferFunction_(StateMemberYaw, StateMemberVroll) = transferFunction_(StateMemberZ, StateMemberVx);
00325     transferFunction_(StateMemberYaw, StateMemberVpitch) = transferFunction_(StateMemberZ, StateMemberVy);
00326     transferFunction_(StateMemberYaw, StateMemberVyaw) = transferFunction_(StateMemberZ, StateMemberVz);
00327     transferFunction_(StateMemberVx, StateMemberAx) = delta;
00328     transferFunction_(StateMemberVy, StateMemberAy) = delta;
00329     transferFunction_(StateMemberVz, StateMemberAz) = delta;
00330 
00331     // (1) Take the square root of a small fraction of the estimateErrorCovariance_ using LL' decomposition
00332     weightedCovarSqrt_ = ((STATE_SIZE + lambda_) * estimateErrorCovariance_).llt().matrixL();
00333 
00334     // (2) Compute sigma points *and* pass them through the transfer function to save
00335     // the extra loop
00336 
00337     // First sigma point is the current state
00338     sigmaPoints_[0] = transferFunction_ * state_;
00339 
00340     // Next STATE_SIZE sigma points are state + weightedCovarSqrt_[ith column]
00341     // STATE_SIZE sigma points after that are state - weightedCovarSqrt_[ith column]
00342     for(size_t sigmaInd = 0; sigmaInd < STATE_SIZE; ++sigmaInd)
00343     {
00344       sigmaPoints_[sigmaInd + 1] = transferFunction_ * (state_ + weightedCovarSqrt_.col(sigmaInd));
00345       sigmaPoints_[sigmaInd + 1 + STATE_SIZE] = transferFunction_ * (state_ - weightedCovarSqrt_.col(sigmaInd));
00346     }
00347 
00348     // (3) Sum the weighted sigma points to generate a new state prediction
00349     state_.setZero();
00350     for(size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
00351     {
00352       state_.noalias() += stateWeights_[sigmaInd] * sigmaPoints_[sigmaInd];
00353     }
00354 
00355     // (4) Now us the sigma points and the predicted state to compute a predicted covariance
00356     estimateErrorCovariance_.setZero();
00357     Eigen::VectorXd sigmaDiff(STATE_SIZE);
00358     for(size_t sigmaInd = 0; sigmaInd < sigmaPoints_.size(); ++sigmaInd)
00359     {
00360       sigmaDiff = (sigmaPoints_[sigmaInd] - state_);
00361       estimateErrorCovariance_.noalias() += covarWeights_[sigmaInd] * (sigmaDiff * sigmaDiff.transpose());
00362     }
00363 
00364     // (5) Not strictly in the theoretical UKF formulation, but necessary here
00365     // to ensure that we actually incorporate the processNoiseCovariance_
00366     estimateErrorCovariance_.noalias() += delta * processNoiseCovariance_;
00367 
00368     // Keep the angles bounded
00369     wrapStateAngles();
00370 
00371     // Mark that we can keep these sigma points
00372     uncorrected_ = true;
00373 
00374     FB_DEBUG("Predicted state is:\n" << state_ <<
00375              "\nPredicted estimate error covariance is:\n" << estimateErrorCovariance_ <<
00376              "\n\n--------------------- /Ukf::predict ----------------------\n");
00377   }
00378 
00379 }


robot_localization
Author(s): Tom Moore
autogenerated on Fri Aug 28 2015 12:26:20