Added ExtendedKalmanFilter class and easyPoint2KalmanFilter example

release/4.3a0
Stephen Williams 2011-08-27 12:28:47 +00:00
parent 295faba745
commit 82fdb0a5f8
8 changed files with 793 additions and 1 deletions

View File

@ -19,6 +19,7 @@ noinst_PROGRAMS += Pose2SLAMExample_advanced # Solves SLAM example from tutoria
noinst_PROGRAMS += Pose2SLAMwSPCG_easy # Solves a simple Pose2 SLAM example with advanced SPCG solver
noinst_PROGRAMS += Pose2SLAMwSPCG_advanced # Solves a simple Pose2 SLAM example with easy SPCG solver
noinst_PROGRAMS += elaboratePoint2KalmanFilter # simple linear Kalman filter on a moving 2D point, but done using factor graphs
noinst_PROGRAMS += easyPoint2KalmanFilter # uses the cool generic templated Kalman filter class to do the same
SUBDIRS = vSLAMexample # visual SLAM examples with 3D point landmarks and 6D camera poses
#----------------------------------------------------------------------------------------------------
# rules to build local programs

View File

@ -6,8 +6,8 @@ Kalman Filter Examples
======================
elaboratePoint2KalmanFilter: simple linear Kalman filter on a moving 2D point, but done using factor graphs
easyPoint2KalmanFilter: uses the cool generic templated Kalman filter class to do the same
errorStateKalmanFilter: simple 1D example of a moving target measured by a accelerometer, incl. drift-rate bias
fullStateKalmanFilter: simple 1D example with a full-state filter
errorStateKalmanFilter: simple 1D example of a moving target measured by a accelerometer, incl. drift-rate bias
2D Pose SLAM
============

View File

@ -0,0 +1,146 @@
/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/*
* easyPoint2KalmanFilter.cpp
*
* simple linear Kalman filter on a moving 2D point, but done using factor graphs
* This example uses the templated ExtendedKalmanFilter class to perform the same
* operations as in elaboratePoint2KalmanFilter
*
* Created on: Aug 19, 2011
* @Author: Frank Dellaert
* @Author: Stephen Williams
*/
/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/**
* @file testExtendedKalmanFilter
* @author Stephen Williams
*/
#include <gtsam/nonlinear/ExtendedKalmanFilter-inl.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/nonlinear/LieValues-inl.h>
#include <gtsam/geometry/Point2.h>
using namespace std;
using namespace gtsam;
// Define Types for Linear System Test
typedef TypedSymbol<Point2, 'x'> LinearKey;
typedef LieValues<LinearKey> LinearValues;
typedef Point2 LinearMeasurement;
int main() {
// Create the Kalman Filter initialization point
Point2 x_initial(0.0, 0.0);
SharedDiagonal P_initial = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1));
// Create an ExtendedKalmanFilter object
ExtendedKalmanFilter<LinearValues,LinearKey> ekf(x_initial, P_initial);
// Now predict the state at t=1, i.e. argmax_{x1} P(x1) = P(x1|x0) P(x0)
// In Kalman Filter notation, this is x_{t+1|t} and P_{t+1|t}
// For the Kalman Filter, this requires a motion model, f(x_{t}) = x_{t+1|t)
// Assuming the system is linear, this will be of the form f(x_{t}) = F*x_{t} + B*u_{t} + w
// where F is the state transition model/matrix, B is the control input model,
// and w is zero-mean, Gaussian white noise with covariance Q
// Note, in some models, Q is actually derived as G*w*G^T where w models uncertainty of some
// physical property, such as velocity or acceleration, and G is derived from physics
//
// For the purposes of this example, let us assume we are using a constant-position model and
// the controls are driving the point to the right at 1 m/s. Then, F = [1 0 ; 0 1], B = [1 0 ; 0 1]
// and u = [1 ; 0]. Let us also assume that the process noise Q = [0.1 0 ; 0 0.1].
Vector u = Vector_(2, 1.0, 0.0);
SharedGaussian Q = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1), true);
// This simple motion can be modeled with a BetweenFactor
// Create Keys
LinearKey x0(0), x1(1);
// Predict delta based on controls
Point2 difference(1,0);
// Create Factor
BetweenFactor<LinearValues,LinearKey> factor1(x0, x1, difference, Q);
// Predict the new value with the EKF class
Point2 x1_predict = ekf.predict(factor1);
x1_predict.print("X1 Predict");
// Now, a measurement, z1, has been received, and the Kalman Filter should be "Updated"/"Corrected"
// This is equivalent to saying P(x1|z1) ~ P(z1|x1)*P(x1)
// For the Kalman Filter, this requires a measurement model h(x_{t}) = \hat{z}_{t}
// Assuming the system is linear, this will be of the form h(x_{t}) = H*x_{t} + v
// where H is the observation model/matrix, and v is zero-mean, Gaussian white noise with covariance R
//
// For the purposes of this example, let us assume we have something like a GPS that returns
// the current position of the robot. Then H = [1 0 ; 0 1]. Let us also assume that the measurement noise
// R = [0.25 0 ; 0 0.25].
SharedGaussian R = noiseModel::Diagonal::Sigmas(Vector_(2, 0.25, 0.25), true);
// This simple measurement can be modeled with a PriorFactor
Point2 z1(1.0, 0.0);
PriorFactor<LinearValues,LinearKey> factor2(x1, z1, R);
// Update the Kalman Filter with the measurement
Point2 x1_update = ekf.update(factor2);
x1_update.print("X1 Update");
// Do the same thing two more times...
// Predict
LinearKey x2(2);
difference = Point2(1,0);
BetweenFactor<LinearValues,LinearKey> factor3(x1, x2, difference, Q);
Point2 x2_predict = ekf.predict(factor1);
x2_predict.print("X2 Predict");
// Update
Point2 z2(2.0, 0.0);
PriorFactor<LinearValues,LinearKey> factor4(x2, z2, R);
Point2 x2_update = ekf.update(factor4);
x2_update.print("X2 Update");
// Do the same thing one more time...
// Predict
LinearKey x3(3);
difference = Point2(1,0);
BetweenFactor<LinearValues,LinearKey> factor5(x2, x3, difference, Q);
Point2 x3_predict = ekf.predict(factor5);
x3_predict.print("X3 Predict");
// Update
Point2 z3(3.0, 0.0);
PriorFactor<LinearValues,LinearKey> factor6(x3, z3, R);
Point2 x3_update = ekf.update(factor6);
x3_update.print("X3 Update");
return 0;
}

View File

@ -0,0 +1,146 @@
/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/**
* @file ExtendedKalmanFilter-inl.h
* @brief Class to perform generic Kalman Filtering using nonlinear factor graphs
* @author Stephen Williams
* @author Chris Beall
*/
#pragma once
#include <gtsam/nonlinear/ExtendedKalmanFilter.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/nonlinear/NonlinearFactor.h>
#include <gtsam/linear/GaussianSequentialSolver.h>
#include <gtsam/linear/GaussianBayesNet.h>
#include <gtsam/linear/GaussianFactorGraph.h>
namespace gtsam {
// Function to compare Ordering entries by value instead of by key
bool compareOrderingValues(const Ordering::value_type& i, const Ordering::value_type& j) {
return i.second < j.second;
}
/* ************************************************************************* */
template<class VALUES, class KEY>
typename ExtendedKalmanFilter<VALUES, KEY>::T ExtendedKalmanFilter<VALUES, KEY>::solve_(const GaussianFactorGraph& linearFactorGraph,
const Ordering& ordering, const VALUES& linearizationPoints, const KEY& lastKey, JacobianFactor::shared_ptr& newPrior) const {
// Extract the Index of the provided last key
gtsam::Index lastIndex = ordering.at(lastKey);
// Solve the linear factor graph, converting it into a linear Bayes Network ( P(x0,x1) = P(x0|x1)*P(x1) )
GaussianSequentialSolver solver(linearFactorGraph);
GaussianBayesNet::shared_ptr linearBayesNet = solver.eliminate();
// Extract the current estimate of x1,P1 from the Bayes Network
VectorValues result = optimize(*linearBayesNet);
T x = linearizationPoints[lastKey].expmap(result[lastIndex]);
// Create a Jacobian Factor from the root node of the produced Bayes Net. This will act as a prior for the next iteration.
// The linearization point of this prior must be moved to the new estimate of x, and the key/index needs to be reset to 0,
// the first key in the next iteration
const GaussianConditional::shared_ptr& cg = linearBayesNet->back();
assert(cg->nrFrontals() == 1);
assert(cg->nrParents() == 0);
// TODO: Find a way to create the correct Jacobian Factor in a single pass
JacobianFactor tmpPrior = JacobianFactor(*cg);
newPrior = JacobianFactor::shared_ptr(
new JacobianFactor(0, tmpPrior.getA(tmpPrior.begin()), tmpPrior.getb() - tmpPrior.getA(tmpPrior.begin()) * result[lastIndex], tmpPrior.get_model()));
return x;
}
/* ************************************************************************* */
template<class VALUES, class KEY>
ExtendedKalmanFilter<VALUES, KEY>::ExtendedKalmanFilter(T x_initial, SharedGaussian P_initial) {
// Set the initial linearization point to the provided mean
x_ = x_initial;
// Create a Jacobian Prior Factor directly P_initial. Since x0 is set to the provided mean, the b vector in the prior will be zero
priorFactor_ = JacobianFactor::shared_ptr(new JacobianFactor(0, P_initial->R(), Vector::Zero(x_initial.dim()), noiseModel::Unit::Create(P_initial->dim())));
}
;
/* ************************************************************************* */
template<class VALUES, class KEY>
typename ExtendedKalmanFilter<VALUES, KEY>::T ExtendedKalmanFilter<VALUES, KEY>::predict(const MotionFactor& motionFactor) {
// TODO: This implementation largely ignores the actual factor symbols. Calling predict() then update() with drastically
// different keys will still compute as if a common key-set was used
// Create Keys
KEY x0 = motionFactor.key1();
KEY x1 = motionFactor.key2();
// Create an elimination ordering
Ordering ordering;
ordering.insert(x0, 0);
ordering.insert(x1, 1);
// Create a set of linearization points
VALUES linearizationPoints;
linearizationPoints.insert(x0, x_);
linearizationPoints.insert(x1, x_);
// Create a Gaussian Factor Graph
GaussianFactorGraph linearFactorGraph;
// Add in the prior on the first state
linearFactorGraph.push_back(priorFactor_);
// Linearize motion model and add it to the Kalman Filter graph
linearFactorGraph.push_back(motionFactor.linearize(linearizationPoints, ordering));
// Solve the factor graph and update the current state estimate and the prior factor for the next iteration
x_ = solve_(linearFactorGraph, ordering, linearizationPoints, x1, priorFactor_);
return x_;
}
/* ************************************************************************* */
template<class VALUES, class KEY>
typename ExtendedKalmanFilter<VALUES, KEY>::T ExtendedKalmanFilter<VALUES, KEY>::update(const MeasurementFactor& measurementFactor) {
// TODO: This implementation largely ignores the actual factor symbols. Calling predict() then update() with drastically
// different keys will still compute as if a common key-set was used
// Create Keys
KEY x0 = measurementFactor.key();
// Create an elimination ordering
Ordering ordering;
ordering.insert(x0, 0);
// Create a set of linearization points
VALUES linearizationPoints;
linearizationPoints.insert(x0, x_);
// Create a Gaussian Factor Graph
GaussianFactorGraph linearFactorGraph;
// Add in the prior on the first state
linearFactorGraph.push_back(priorFactor_);
// Linearize measurement factor and add it to the Kalman Filter graph
linearFactorGraph.push_back(measurementFactor.linearize(linearizationPoints, ordering));
// Solve the factor graph and update the current state estimate and the prior factor for the next iteration
x_ = solve_(linearFactorGraph, ordering, linearizationPoints, x0, priorFactor_);
return x_;
}
} // namespace gtsam

View File

@ -0,0 +1,69 @@
/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/**
* @file ExtendedKalmanFilter.h
* @brief Class to perform generic Kalman Filtering using nonlinear factor graphs
* @author Stephen Williams
* @author Chris Beall
*/
// \callgraph
#pragma once
#include <gtsam/nonlinear/NonlinearFactorGraph-inl.h>
#include <gtsam/nonlinear/NonlinearFactor.h>
namespace gtsam {
/**
* This is a generic Extended Kalman Filter class implemented using nonlinear factors. GTSAM
* basically does SRIF with LDL to solve the filter problem, making this an efficient, numerically
* stable Kalman Filter implementation.
*
* The Kalman Filter relies on two models: a motion model that predicts the next state using
* the current state, and a measurement model that predicts the measurement value at a given
* state. Because these two models are situation-dependent, base classes for each have been
* provided above, from which the user must derive a class and incorporate the actual modeling
* equations. A pointer to an instance of each of these classes must be given to the Kalman
* Filter at creation, allowing the Kalman Filter to create factors as needed.
*
* The Kalman Filter class provides a "predict" function and an "update" function to perform
* these steps independently, as well as a "predictAndUpdate" that combines both steps for some
* computational savings.
*/
template<class VALUES, class KEY>
class ExtendedKalmanFilter {
public:
typedef boost::shared_ptr<ExtendedKalmanFilter<VALUES, KEY> > shared_ptr;
typedef typename KEY::Value T;
typedef NonlinearFactor2<VALUES, KEY, KEY> MotionFactor;
typedef typename MotionFactor::shared_ptr SharedMotionFactor;
typedef NonlinearFactor1<VALUES, KEY> MeasurementFactor;
typedef typename MeasurementFactor::shared_ptr SharedMeasurementFactor;
protected:
JacobianFactor::shared_ptr priorFactor_;
T x_;
T solve_(const GaussianFactorGraph& linearFactorGraph, const Ordering& ordering, const VALUES& linearizationPoints, const KEY& x,
JacobianFactor::shared_ptr& newPrior) const;
public:
ExtendedKalmanFilter(T x_initial, SharedGaussian P_initial);
T predict(const MotionFactor& motionFactor);
T update(const MeasurementFactor& measurementFactor);
};
} // namespace

View File

@ -34,6 +34,9 @@ sources += GaussianISAM2.cpp
# Nonlinear constraints
headers += NonlinearEquality.h NonlinearConstraint.h
# Kalman Filter
headers += ExtendedKalmanFilter.h ExtendedKalmanFilter-inl.h
#----------------------------------------------------------------------------------------------------
# Create a libtool library that is not installed
# It will be packaged in the toplevel libgtsam.la as specfied in ../Makefile.am

View File

@ -22,6 +22,7 @@ check_PROGRAMS += testBoundingConstraint
check_PROGRAMS += testNonlinearEqualityConstraint
check_PROGRAMS += testPose2SLAMwSPCG
check_PROGRAMS += testGaussianISAM2
check_PROGRAMS += testExtendedKalmanFilter
# only if serialization is available
if ENABLE_SERIALIZATION

View File

@ -0,0 +1,426 @@
/* ----------------------------------------------------------------------------
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/**
* @file testExtendedKalmanFilter
* @author Stephen Williams
*/
// TODO: Perform tests with nontrivial data
// TODO: Perform tests with nonlinear data
#include <CppUnitLite/TestHarness.h>
#include <gtsam/nonlinear/ExtendedKalmanFilter-inl.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/nonlinear/NonlinearFactor.h>
#include <gtsam/nonlinear/LieValues-inl.h>
#include <gtsam/geometry/Point2.h>
using namespace gtsam;
// Define Types for Linear System Test
typedef TypedSymbol<Point2, 'x'> LinearKey;
typedef LieValues<LinearKey> LinearValues;
typedef Point2 LinearMeasurement;
/* ************************************************************************* */
TEST( ExtendedKalmanFilter, linear ) {
// Create the Kalman Filter initialization point
Point2 x_initial(0.0, 0.0);
SharedDiagonal P_initial = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1));
// Create an ExtendedKalmanFilter object
ExtendedKalmanFilter<LinearValues,LinearKey> ekf(x_initial, P_initial);
// Create the keys for our example
LinearKey x0(0), x1(1), x2(2), x3(3);
// Create the controls and measurement properties for our example
double dt = 1.0;
Vector u = Vector_(2, 1.0, 0.0);
Point2 difference(u*dt);
SharedGaussian Q = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1), true);
Point2 z1(1.0, 0.0);
Point2 z2(2.0, 0.0);
Point2 z3(3.0, 0.0);
SharedGaussian R = noiseModel::Diagonal::Sigmas(Vector_(2, 0.25, 0.25), true);
// Create the set of expected output values
Point2 expected1(1.0, 0.0);
Point2 expected2(2.0, 0.0);
Point2 expected3(3.0, 0.0);
// Run iteration 1
// Create motion factor
BetweenFactor<LinearValues,LinearKey> factor1(x0, x1, difference, Q);
Point2 predict1 = ekf.predict(factor1);
EXPECT(assert_equal(expected1,predict1));
// Create the measurement factor
PriorFactor<LinearValues,LinearKey> factor2(x1, z1, R);
Point2 update1 = ekf.update(factor2);
EXPECT(assert_equal(expected1,update1));
// Run iteration 2
BetweenFactor<LinearValues,LinearKey> factor3(x1, x2, difference, Q);
Point2 predict2 = ekf.predict(factor3);
EXPECT(assert_equal(expected2,predict2));
PriorFactor<LinearValues,LinearKey> factor4(x2, z2, R);
Point2 update2 = ekf.update(factor4);
EXPECT(assert_equal(expected2,update2));
// Run iteration 3
BetweenFactor<LinearValues,LinearKey> factor5(x2, x3, difference, Q);
Point2 predict3 = ekf.predict(factor5);
EXPECT(assert_equal(expected3,predict3));
PriorFactor<LinearValues,LinearKey> factor6(x3, z3, R);
Point2 update3 = ekf.update(factor6);
EXPECT(assert_equal(expected3,update3));
return;
}
// Create Motion Model Factor
class NonlinearMotionModel : public NonlinearFactor2<LinearValues,LinearKey,LinearKey> {
public:
typedef typename LinearKey::Value T;
private:
typedef NonlinearFactor2<LinearValues,LinearKey,LinearKey> Base;
typedef NonlinearMotionModel This;
protected:
Matrix Q_;
Matrix Q_invsqrt_;
public:
NonlinearMotionModel(){}
NonlinearMotionModel(const LinearKey& key1, const LinearKey& key2) :
Base(noiseModel::Diagonal::Sigmas(Vector_(2, 1.0, 1.0)), key1, key2), Q_(2,2) {
// Initialize motion model parameters:
// w is vector of motion noise sigmas. The final covariance is calculated as G*w*w'*G'
// TODO: Ultimately, the value Q^-1/2 is needed. This can be calculated/derived symbolically, eliminating the
// need for the noiseModel_, and improving computational performance.
Matrix G(2,2);
Matrix w(2,2);
G << 1.0, 0.0, 0.0, 1.0;
w << 1.0, 0.0, 0.0, 1.0;
w = G*w;
Q_ = w*w.transpose();
Q_invsqrt_ = inverse_square_root(Q_);
}
virtual ~NonlinearMotionModel() {}
// Calculate the next state prediction using the nonlinear function f()
T f(const T& x_t0) const {
// Calculate f(x)
double x = x_t0.x() * x_t0.x();
double y = x_t0.x() * x_t0.y();
T x_t1(x,y);
// In this example, the noiseModel remains constant
return x_t1;
}
// Calculate the Jacobian of the nonlinear function f()
Matrix F(const T& x_t0) const {
// Calculate Jacobian of f()
Matrix F = Matrix(2,2);
F(0,0) = 2*x_t0.x();
F(0,1) = 0.0;
F(1,0) = x_t0.y();
F(1,1) = x_t0.x();
return F;
}
// Calculate the inverse square root of the motion model covariance, Q
Matrix QInvSqrt(const T& x_t0) const {
// This motion model has a constant covariance
return Q_invsqrt_;
}
/* print */
virtual void print(const std::string& s = "") const {
std::cout << s << ": NonlinearMotionModel\n";
std::cout << " key1: " << (std::string) key1_ << std::endl;
std::cout << " key2: " << (std::string) key2_ << std::endl;
}
/** Check if two factors are equal. Note type is IndexFactor and needs cast. */
virtual bool equals(const NonlinearFactor2<LinearValues,LinearKey,LinearKey>& f, double tol = 1e-9) const {
const This *e = dynamic_cast<const This*> (&f);
return (e != NULL) && (key1_ == e->key1_) && (key2_ == e->key2_);
}
/**
* calculate the error of the factor
* Override for systems with unusual noise models
*/
virtual double error(const LinearValues& c) const {
Vector w = whitenedError(c);
return 0.5 * w.dot(w);
}
/** get the dimension of the factor (number of rows on linearization) */
size_t dim() const {
return 2;
}
/** Vector of errors, whitened ! */
Vector whitenedError(const LinearValues& c) const {
return QInvSqrt(c[key1_])*unwhitenedError(c);
}
/**
* Linearize a non-linearFactor2 to get a GaussianFactor
* Ax-b \approx h(x1+dx1,x2+dx2)-z = h(x1,x2) + A2*dx1 + A2*dx2 - z
* Hence b = z - h(x1,x2) = - error_vector(x)
*/
boost::shared_ptr<GaussianFactor> linearize(const LinearValues& c, const Ordering& ordering) const {
const X1& x1 = c[key1_];
const X2& x2 = c[key2_];
Matrix A1, A2;
Vector b = -evaluateError(x1, x2, A1, A2);
const Index var1 = ordering[key1_], var2 = ordering[key2_];
SharedDiagonal constrained =
boost::shared_dynamic_cast<noiseModel::Constrained>(this->noiseModel_);
if (constrained.get() != NULL) {
return JacobianFactor::shared_ptr(new JacobianFactor(var1, A1, var2,
A2, b, constrained));
}
// "Whiten" the system before converting to a Gaussian Factor
Matrix Qinvsqrt = QInvSqrt(x1);
A1 = Qinvsqrt*A1;
A2 = Qinvsqrt*A2;
b = Qinvsqrt*b;
return GaussianFactor::shared_ptr(new JacobianFactor(var1, A1, var2,
A2, b, noiseModel::Unit::Create(b.size())));
}
/** vector of errors */
Vector evaluateError(const T& p1, const T& p2,
boost::optional<Matrix&> H1 = boost::none, boost::optional<Matrix&> H2 =
boost::none) const {
// error = p2 - f(p1)
// H1 = d error / d p1 = -d f/ d p1 = -F
// H2 = d error / d p2 = I
T prediction = f(p1);
if(H1){
*H1 = -F(p1);
}
if(H2){
*H2 = eye(dim());
}
// Return the error between the prediction and the supplied value of p2
return prediction.logmap(p2);
}
};
// Create Measurement Model Factor
class NonlinearMeasurementModel : public NonlinearFactor1<LinearValues,LinearKey> {
public:
typedef typename LinearKey::Value T;
private:
typedef NonlinearFactor1<LinearValues,LinearKey> Base;
typedef NonlinearMeasurementModel This;
protected:
Vector z_; /** The measurement */
Matrix R_; /** The measurement error covariance */
Matrix R_invsqrt_; /** The inv sqrt of the measurement error covariance */
public:
NonlinearMeasurementModel(){}
NonlinearMeasurementModel(const LinearKey& key, Vector z) :
Base(noiseModel::Diagonal::Sigmas(Vector_(2, 1.0, 1.0)), key), z_(z), R_(1,1) {
// Initialize nonlinear measurement model parameters:
// z(t) = H*x(t) + v
// where v ~ N(0, noiseModel_)
R_ << 1.0;
R_invsqrt_ = inverse_square_root(R_);
}
virtual ~NonlinearMeasurementModel() {}
// Calculate the predicted measurement using the nonlinear function h()
// Byproduct: updates Jacobian H, and noiseModel (R)
Vector h(const T& x_t1) const {
// Calculate prediction
Vector z_hat(1);
z_hat(0) = 2*x_t1.x()*x_t1.x() - x_t1.x()*x_t1.y() - 2.5*x_t1.x() + 7*x_t1.y();
return z_hat;
}
Matrix H(const T& x_t1) const {
// Update Jacobian
Matrix H(1,2);
H(0,0) = 4*x_t1.x() - x_t1.y() - 2.5;
H(0,1) = -1*x_t1.x() + 7;
return H;
}
// Calculate the inverse square root of the motion model covariance, Q
Matrix RInvSqrt(const T& x_t0) const {
// This motion model has a constant covariance
return R_invsqrt_;
}
/* print */
virtual void print(const std::string& s = "") const {
std::cout << s << ": NonlinearMeasurementModel\n";
std::cout << " key: " << (std::string) key_ << std::endl;
}
/** Check if two factors are equal. Note type is IndexFactor and needs cast. */
virtual bool equals(const NonlinearFactor1<LinearValues,LinearKey>& f, double tol = 1e-9) const {
const This *e = dynamic_cast<const This*> (&f);
return (e != NULL) && (key_ == e->key_);
}
/**
* calculate the error of the factor
* Override for systems with unusual noise models
*/
virtual double error(const LinearValues& c) const {
Vector w = whitenedError(c);
return 0.5 * w.dot(w);
}
/** get the dimension of the factor (number of rows on linearization) */
size_t dim() const {
return 1;
}
/** Vector of errors, whitened ! */
Vector whitenedError(const LinearValues& c) const {
return RInvSqrt(c[key_])*unwhitenedError(c);
}
/**
* Linearize a nonlinearFactor1 measurement factor into a GaussianFactor
* Ax-b \approx h(x1+dx1)-z = h(x1) + A1*dx1 - z
* Hence b = z - h(x1) = - error_vector(x)
*/
boost::shared_ptr<GaussianFactor> linearize(const LinearValues& c, const Ordering& ordering) const {
const X& x1 = c[key_];
Matrix A1;
Vector b = -evaluateError(x1, A1);
const Index var1 = ordering[key_];
SharedDiagonal constrained =
boost::shared_dynamic_cast<noiseModel::Constrained>(this->noiseModel_);
if (constrained.get() != NULL) {
return JacobianFactor::shared_ptr(new JacobianFactor(var1, A1, b, constrained));
}
// "Whiten" the system before converting to a Gaussian Factor
Matrix Rinvsqrt = RInvSqrt(x1);
A1 = Rinvsqrt*A1;
b = Rinvsqrt*b;
return GaussianFactor::shared_ptr(new JacobianFactor(var1, A1, b, noiseModel::Unit::Create(b.size())));
}
/** vector of errors */
Vector evaluateError(const LinearKey::Value& p, boost::optional<Matrix&> H1 = boost::none) const {
// error = z - h(p)
// H = d error / d p = -d h/ d p = -H
Vector z_hat = h(p);
if(H1){
*H1 = -H(p);
}
// Return the error between the prediction and the supplied value of p2
return z_ - z_hat;
}
};
/* ************************************************************************* */
TEST( ExtendedKalmanFilter, nonlinear ) {
// Create the set of expected output values (generated using Matlab Kalman Filter)
Point2 expected_predict[10];
Point2 expected_update[10];
expected_predict[1] = Point2(0.81,0.99);
expected_update[1] = Point2(0.824926197027,0.29509808);
expected_predict[2] = Point2(0.680503230541,0.24343413);
expected_update[2] = Point2(0.773360464065,0.43518355);
expected_predict[3] = Point2(0.598086407378,0.33655375);
expected_update[3] = Point2(0.908781566884,0.60766713);
expected_predict[4] = Point2(0.825883936308,0.55223668);
expected_update[4] = Point2(1.23221370495,0.74372849);
expected_predict[5] = Point2(1.51835061468,0.91643243);
expected_update[5] = Point2(1.32823362774,0.855855);
expected_predict[6] = Point2(1.76420456986,1.1367754);
expected_update[6] = Point2(1.36378040243,1.0623832);
expected_predict[7] = Point2(1.85989698605,1.4488574);
expected_update[7] = Point2(1.24068063304,1.3431964);
expected_predict[8] = Point2(1.53928843321,1.6664778);
expected_update[8] = Point2(1.04229257957,1.4856408);
expected_predict[9] = Point2(1.08637382142,1.5484724);
expected_update[9] = Point2(1.13201933157,1.5795507);
expected_predict[10] = Point2(1.28146776705,1.7880819);
expected_update[10] = Point2(1.22159588179,1.7434982);
// Create the Kalman Filter initialization point
Point2 x_initial(0.90, 1.10);
SharedDiagonal P_initial = noiseModel::Diagonal::Sigmas(Vector_(2, 0.1, 0.1));
// Create an ExtendedKalmanFilter object
ExtendedKalmanFilter<LinearValues,LinearKey> ekf(x_initial, P_initial);
// Enter Predict-Update Loop
Point2 x_predict, x_update;
for(unsigned int i = 1; i < 9; ++i){
// Create motion factor
NonlinearMotionModel motionFactor(LinearKey(i-1), LinearKey(i));
x_predict = ekf.predict(motionFactor);
// Create a measurement factor
NonlinearMeasurementModel measurementFactor(LinearKey(i), Vector_(1, (double)i));
x_update = ekf.update(measurementFactor);
EXPECT(assert_equal(expected_predict[i],x_predict, 1e-6));
EXPECT(assert_equal(expected_update[i], x_update, 1e-6));
}
return;
}
/* ************************************************************************* */
int main() { TestResult tr; return TestRegistry::runAllTests(tr); }
/* ************************************************************************* */