indii/ml/filter/UnscentedTransformation.hpp

00001 #ifndef INDII_ML_FILTER_UNSCENTEDTRANSFORMATION
00002 #define INDII_ML_FILTER_UNSCENTEDTRANSFORMATION
00003 
00004 #include "../aux/vector.hpp"
00005 #include "../aux/GaussianPdf.hpp"
00006 
00007 #include "UnscentedTransformationModel.hpp"
00008 #include "../ode/ParameterCollection.hpp"
00009 
00010 namespace indii {
00011   namespace ml {
00012     namespace filter {
00013 
00014 /**
00015  * Unscented transformation.
00016  *
00017  * @author Lawrence Murray <lawrence@indii.org>
00018  * @version $Rev: 436 $
00019  * @date $Date: 2008-04-28 00:23:37 +0100 (Mon, 28 Apr 2008) $
00020  *
00021  * The unscented transformation propagates a Gaussian distributed random
00022  * variable through a nonlinear function, estimating its new mean and
00023  * covariance after application of the function. This is achieved by
00024  * deterministic sampling of a set of <i>sigma points</i> from the
00025  * distribution, propagation of these points through the function and
00026  * recalculation of the mean and covariance.
00027  *
00028  * @section UnscentedTransformation_details Details
00029  *
00030  * As described in @ref Julier1997 "Julier & Uhlmann (1997)" and @ref Wan2000
00031  * "Wan & van der Merwe (2000)", let \f$L\f$ be the number of dimensions of
00032  * the input random variable \f$\mathbf{x}\f$, with mean
00033  * \f$\mathbf{\bar{x}}\f$ and covariance matrix \f$P_x\f$. Let:
00034  *
00035  * \f[\lambda = \alpha^2 (L + \kappa) - L\f]
00036  *
00037  * \f$2L + 1\f$ sigma points \f$\mathcal{X}_i\f$ are defined as:
00038  *
00039  * \f[
00040  * \begin{array}{lcll}
00041  *   \mathcal{X}_0 & = & \mathbf{\bar{x}} \\
00042  *   \mathcal{X}_i & = & \mathbf{\bar{x}} + \left(\sqrt{(L+\lambda)P_x}
00043  *     \right)_i & i = 1,\ldots,L \\
00044  *   \mathcal{X}_i & = & \mathbf{\bar{x}} - \left(\sqrt{(L+\lambda)P_x}
00045  *     \right)_{i-L} & i = L+1,\ldots,2L \\
00046  * \end{array}
00047  * \f]
00048  *
00049  * where \f$\left(\sqrt{(L+\lambda)P_x}\right)_i\f$ is the \f$i\f$th column of
00050  * the matrix square root (Cholesky decomposition).
00051  *
00052  * Mean weights \f$W^{(m)}_i\f$ and covariance weights \f$W^{(c)}_i\f$ are
00053  * defined as:
00054  *
00055  * \f[
00056  * \begin{array}{lclcll}
00057  *   W^{(m)}_0 & = & & & \lambda/(L+\lambda) \\
00058  *   W^{(c)}_0 & = & & & \lambda/(L+\lambda)+(1-\alpha^2+\beta) \\
00059  *   W^{(m)}_i & = & W^{(c)}_i & = & 1/\left(2\left(L+\lambda\right)\right) &
00060  *     i = 1,\ldots,2L \\
00061  * \end{array}
00062  * \f]
00063  *
00064  * Each sigma point is then propagated through the given function \f$f\f$:
00065  *
00066  * \f[
00067  * \begin{array}{lcll}
00068  *   \mathcal{Y}_i & = & f(\mathcal{X}_i) & i = 0,\ldots,2L \\
00069  * \end{array}
00070  * \f]
00071  *
00072  * Finally, the mean and covariance of \f$f(\mathbf{x})\f$ are estimated as:
00073  *
00074  * \f{eqnarray*}
00075  *   \mathbf{\bar{y}} & \approx & \sum_{i=0}^{2L}W^{(m)}_i\mathcal{Y}_i \\
00076  *   P_y & \approx & \sum_{i=0}^{2L}W^{(c)}_i
00077  *     (\mathcal{Y}_i-\mathbf{\bar{y}})(\mathcal{Y}_i-\mathbf{\bar{y}})^T \\
00078  * \f}
00079  *
00080  * and the cross-correlation matrix \f$P_{xy}\f$ as:
00081  *
00082  * \f[P_{xy} \approx \sum_{i=0}^{2L}W^{(c)}_i
00083  * (\mathcal{X}_i-\mathbf{\bar{x}})(\mathcal{Y}_i-\mathbf{\bar{y}})^T\f]
00084  *
00085  * @section implementation Implementation
00086  *
00087  * This particular implementation uses a modified form of the above
00088  * formulas so that it need not hold the complete set of sigma points
00089  * in memory, as required in the calculation of \f$P_y\f$ above.
00090  *
00091  * First, we rename \f$\mathbf{\bar{y}}\f$ to \f$\mathbf{\bar{y}}_m\f$;
00092  * the mean calculated using mean weights
00093  * \f$W^{(m)}_0,\ldots,W^{(m)}_{2L}\f$. We then define a second mean
00094  * \f$\mathbf{\bar{y}}_c\f$ calculated using covariance weights
00095  * \f$W^{(c)}_0,\ldots,W^{(c)}_{2L}\f$:
00096  *
00097  * \f{eqnarray*}
00098  *   \mathbf{\bar{y}}_c & \approx & \sum_{i=0}^{2L}W^{(c)}_i\mathcal{Y}_i
00099  * \f}
00100  *
00101  * Noting that \f$W^{(c)}_i = W^{(m)}_i\f$ for \f$i = 1,\ldots,2L\f$, the two
00102  * means need not be calculated independently.
00103  *
00104  * The calculation of the covariance \f$P_y\f$ can then be modified to:
00105  *
00106  * \f{eqnarray*}
00107  *   P_y & \approx & \sum_{i=0}^{2L}W^{(c)}_i
00108  *     (\mathcal{Y}_i-\mathbf{\bar{y}}_m)
00109  *     (\mathcal{Y}_i-\mathbf{\bar{y}}_m)^T \\
00110  *   & = & \sum_{i=0}^{2L}W^{(c)}_i
00111  *     (\mathcal{Y}_i\mathcal{Y}_i^T - \mathcal{Y}_i\mathbf{\bar{y}}_m^T -
00112  *     \mathbf{\bar{y}}_m\mathcal{Y}_i^T +
00113  *     \mathbf{\bar{y}}_m\mathbf{\bar{y}}_m^T) \\
00114  *   & = & \sum_{i=0}^{2L}W^{(c)}_i\mathcal{Y}_i\mathcal{Y}_i^T -
00115  *     \mathbf{\bar{y}}_c\mathbf{\bar{y}}_m^T -
00116  *     \mathbf{\bar{y}}_m\mathbf{\bar{y}}_c^T +
00117  *     \sum_{i=0}^{2L}W^{(c)}_i\mathbf{\bar{y}}_m\mathbf{\bar{y}}_m^T
00118  * \f}
00119  *
00120  * @section UnscentedTransformation_references References
00121  *
00122  * @anchor Julier1997
00123  * Julier, S.J. & Uhlmann, J.K. A New Extension of the Kalman %Filter
00124  * to nonlinear Systems <i>The Proceedings of AeroSense: The 11th
00125  * International Symposium on Aerospace/Defense Sensing, Simulation
00126  * and Controls, Multi Sensor Fusion, Tracking and Resource
00127  * Management</i>, <b>1997</b>.
00128  *
00129  * @anchor Wan2000
00130  * Wan, E.A. & van der Merwe, R. The Unscented Kalman %Filter for
00131  * Nonlinear Estimation. <i>Proceedings of IEEE Symposium on Adaptive
00132  * Systems for Signal Processing Communications and Control
00133  * (AS-SPCC)</i>, <b>2000</b>.
00134  */
00135 template <class T = unsigned int>
00136 class UnscentedTransformation : public indii::ml::ode::ParameterCollection {
00137 public:
00138   /**
00139    * Parameter indices.
00140    */
00141   enum Parameter {
00142     /**
00143      * \f$\alpha\f$; spread of the sigma points about
00144      * \f$\mathbf{\bar{x}}\f$. Usually set to a small positive value.
00145      */
00146     ALPHA,
00147 
00148     /**
00149      * \f$\beta\f$; incorporates prior knowledge of the distribution of
00150      * \f$\mathbf{x}\f$. Value of 2 is optimal for Gaussian distributions.
00151      */
00152     BETA,
00153 
00154     /**
00155      * \f$\kappa\f$; secondary scaling parameter. Usually set to zero.
00156      */
00157     KAPPA
00158   };
00159 
00160   /**
00161    * Create new transformation with default parameter values. Default values
00162    * are obtained from UnscentedTransformationDefaults.
00163    *
00164    * @param model Model representing the function \f$f\f$. Callee claims
00165    * ownership.
00166    */
00167   UnscentedTransformation(UnscentedTransformationModel<T>& model);
00168 
00169   /**
00170    * Destructor.
00171    */
00172   ~UnscentedTransformation();
00173 
00174   /**
00175    * Apply the unscented transformation.
00176    *
00177    * @param x \f$P(\mathbf{x})\f$; distribution over the random variable to
00178    * propagate through the function.
00179    * @param delta \f$\Delta t\f$; length of time through which to propagate
00180    * the distribution, if relevant.
00181    * @param P If specified, the cross correlation matrix between the input and
00182    * output of the function is estimated using sigma points and written into
00183    * this matrix. The matrix should be of size \f$n \times m\f$, where \f$n\f$
00184    * is the dimensionality of the input space and \f$m\f$ the dimensionality
00185    * of the output space.
00186    *
00187    * @return \f$P(f(\mathbf{x}))\f$; distribution over the function output.
00188    */
00189   indii::ml::aux::GaussianPdf transform(const indii::ml::aux::GaussianPdf& x,
00190       T delta = 0, indii::ml::aux::matrix* P = NULL);
00191 
00192 private:
00193   /**
00194    * Number of parameters
00195    */
00196   static const unsigned int NUM_PARAMETERS = 3;
00197 
00198   /**
00199    * Model defining function through which to propagate points.
00200    */
00201   UnscentedTransformationModel<T>& model;
00202 
00203 };
00204 
00205     }
00206   }
00207 }
00208 
00209 #include "UnscentedTransformationDefaults.hpp"
00210 
00211 #include <math.h>
00212 
00213 #include "boost/numeric/bindings/traits/ublas_vector.hpp"
00214 #include "boost/numeric/bindings/traits/ublas_matrix.hpp"
00215 #include "boost/numeric/bindings/traits/ublas_symmetric.hpp"
00216 #include "boost/numeric/bindings/lapack/lapack.hpp"
00217 
00218 using namespace indii::ml::filter;
00219 
00220 namespace aux = indii::ml::aux;
00221 namespace ublas = boost::numeric::ublas;
00222 namespace lapack = boost::numeric::bindings::lapack;
00223 
00224 template <class T>
00225 UnscentedTransformation<T>::UnscentedTransformation(
00226     UnscentedTransformationModel<T>& model) :
00227     ParameterCollection(NUM_PARAMETERS), model(model) {
00228   setParameter(ALPHA, UnscentedTransformationDefaults::ALPHA);
00229   setParameter(BETA, UnscentedTransformationDefaults::BETA);
00230   setParameter(KAPPA, UnscentedTransformationDefaults::KAPPA);
00231 }
00232 
00233 template <class T>
00234 UnscentedTransformation<T>::~UnscentedTransformation() {
00235   //
00236 }
00237 
00238 template <class T>
00239 aux::GaussianPdf UnscentedTransformation<T>::transform(
00240     const aux::GaussianPdf& x, T delta, aux::matrix* P) {
00241   /* pre-condition */
00242   assert (x.getDimensions() > 0);
00243 
00244   const unsigned int L = x.getDimensions();
00245   const double LAMBDA = pow(getParameter(ALPHA), 2.0) *
00246       (L + getParameter(KAPPA)) - L;
00247 
00248   /* calculate weights */
00249   const double W_m_0 = LAMBDA / ((double)L + LAMBDA);
00250   const double W_c_0 = W_m_0 + (1.0 - pow(getParameter(ALPHA), 2.0) +
00251       getParameter(BETA));
00252   const double W_m_i = 1.0 / (2.0 * ((double)L + LAMBDA));
00253   const double W_c_i = W_m_i;
00254   //const double W_m_t = 2.0 * (double)L * W_m_i + W_m_0; // total mean weight
00255   const double W_c_t = 2.0 * (double)L * W_c_i + W_c_0; // total cov weight
00256 
00257   /* calculate Cholesky decomposition of covariance matrix */
00258   int err;  
00259   aux::matrix tmp(x.getCovariance());
00260   err = lapack::potrf('L', tmp);
00261   assert(err == 0);
00262   ublas::triangular_adaptor<aux::matrix, ublas::lower> cholesky(tmp);
00263 
00264   /* calculate sigma points 1,...,2L, propagate and sum as we go */
00265   unsigned int i, j;
00266   const aux::vector &x_mu = x.getExpectation();
00267   aux::matrix A(sqrt((double)L + LAMBDA) * cholesky);
00268 
00269   /* dimensionality of output unknown, so initialise vectors using sigma point
00270    * 1 */
00271   aux::vector X(x_mu + ublas::column(A,0));
00272   aux::vector X_mu_m(X);
00273   aux::vector Y(model.propagate(X,delta));
00274   aux::vector Y_mu_m(Y);
00275   aux::symmetric_matrix Y_sigma(outer_prod(Y,Y));
00276   aux::matrix* XY_sigma = P;
00277   if (XY_sigma != NULL) {
00278     *XY_sigma = outer_prod(X,Y);
00279   }
00280 
00281   /* include sigma point L+1 */
00282   noalias(X) = x_mu - ublas::column(A,0);
00283   noalias(X_mu_m) += X;
00284   noalias(Y) = model.propagate(X,delta);
00285   noalias(Y_mu_m) += Y;
00286   noalias(Y_sigma) += outer_prod(Y,Y);
00287   if (XY_sigma != NULL) {
00288     noalias(*XY_sigma) += outer_prod(X,Y);
00289   }
00290 
00291   /* include sigma points 2,...,L,L+2,...,2L */
00292   for (i = 2; i <= L; i++) {
00293     /* sigma point i */
00294     noalias(X) = x_mu + ublas::column(A,i-1);
00295     noalias(X_mu_m) += X;
00296     noalias(Y) = model.propagate(X,delta);
00297     noalias(Y_mu_m) += Y;
00298     noalias(Y_sigma) += outer_prod(Y,Y);
00299     if (XY_sigma != NULL) {
00300       noalias(*XY_sigma) += outer_prod(X,Y);
00301     }
00302 
00303     /* sigma point L+i */
00304     noalias(X) = x_mu - ublas::column(A,i-1);
00305     noalias(X_mu_m) += X;
00306     noalias(Y) = model.propagate(X,delta);
00307     noalias(Y_mu_m) += Y;
00308     noalias(Y_sigma) += outer_prod(Y,Y);
00309     if (XY_sigma != NULL) {
00310       noalias(*XY_sigma) += outer_prod(X,Y);
00311     }
00312   }
00313 
00314   aux::vector X_mu_c(X_mu_m*W_c_i);
00315   aux::vector Y_mu_c(Y_mu_m*W_c_i);
00316   X_mu_m *= W_m_i;
00317   Y_mu_m *= W_m_i;
00318 
00319   /* include sigma point 0 */
00320   noalias(X) = x_mu;
00321   noalias(X_mu_m) += W_m_0*X;
00322   noalias(X_mu_c) += W_c_0*X;
00323   noalias(Y) = model.propagate(X,delta);
00324   noalias(Y_mu_m) += W_m_0*Y;
00325   noalias(Y_mu_c) += W_c_0*Y;
00326 
00327   /* finalise covariance */
00328   Y_sigma *= W_c_i;
00329   noalias(Y_sigma) += W_c_0*outer_prod(Y,Y);
00330   noalias(Y_sigma) += W_c_t*outer_prod(Y_mu_m,Y_mu_m);
00331   
00332   //noalias(Y_sigma) -= outer_prod(Y_mu_m,Y_mu_c) + outer_prod(Y_mu_c,Y_mu_m);
00333   aux::matrix tmp1(outer_prod(Y_mu_m,Y_mu_c));
00334   noalias(Y_sigma) -= tmp1 + trans(tmp1);
00335 
00336   /* finalise cross-correlation */
00337   if (XY_sigma != NULL) {
00338     *XY_sigma *= W_c_i;
00339     noalias(*XY_sigma) += W_c_0*outer_prod(X,Y);
00340     noalias(*XY_sigma) += W_c_t*outer_prod(X_mu_m,Y_mu_m);
00341     noalias(*XY_sigma) -= outer_prod(X_mu_m,Y_mu_c)+outer_prod(X_mu_c,Y_mu_m);
00342   }
00343 
00344   /* create distribution */
00345   return aux::GaussianPdf(Y_mu_m, Y_sigma);
00346 }
00347 
00348 #endif

Generated on Wed Dec 17 15:11:57 2008 for dysii Dynamical Systems Library by  doxygen 1.5.3