A System Model in C++
After learning the basic principles of writing a MEX file with the handy Eigen wrappers provided by the Nonlinear Estimation Toolbox, let's program a first useful functionality in C++. We start with a general system model/measurement model. Implementing such a model in C++ requires two parts:
the usual MATLAB class that inherits from the system model/measurement model to be implemented and
the actual MEX file that implements the systemEquation()/measurementEquation() method.
Here, we consider the nonlinear system model from the Getting Started guide. Although this model can be written easily in MATLAB, it serves well as an illustrative example for a C++ implementation. First, we write the required MATLAB class:
TargetSysModelMex.m
classdef TargetSysModelMex < SystemModel methods function obj = TargetSysModelMex() obj.deltaT = 0.1; sysNoise = Gaussian(zeros(2, 1), [1e-1 1e-3]); obj.setNoise(sysNoise); end function predictedStates = systemEquation(obj, stateSamples, noiseSamples) predictedStates = targetSystemEquation(stateSamples, noiseSamples, obj.deltaT); end end properties (Access = 'private') deltaT; end end
The only significant difference compared to the original TargetSysModel.m file is that the systemEquation() code is moved into a MEX file called targetSystemEquation(). Note also that we pass the step size \(\Delta t\) as a parameter. In doing so, we can change it from MATLAB without recompiling the MEX file. Next, we turn to the crucial part: the C++ implementation.
targetSystemEquation.cpp
#include <Mex/Mex.h> void mexFunction(int numOutputs, mxArray *outputs[], int numInputs, const mxArray *inputs[]) { try { // Check for proper number of arguments if (numInputs != 3) { throw std::invalid_argument("Three inputs are required."); } if (numOutputs != 1) { throw std::invalid_argument("One output is required."); } // First parameter contains the state samples Mex::ConstMatrix<double, 5, Eigen::Dynamic> stateSamples(inputs[0]); // Second parameter contains the noise samples Mex::ConstMatrix<double, 2, Eigen::Dynamic> noiseSamples(inputs[1]); // Check for the same number of state samples and noise samples if (stateSamples.cols() != noiseSamples.cols()) { throw std::invalid_argument("Different number of samples."); } // Third parameter is the discrete time step const double deltaT = mxGetScalar(inputs[2]); // Allocate memory for predicted state samples const int numSamples = stateSamples.cols(); Mex::OutputMatrix<double, 5, Eigen::Dynamic> predictedStates(5, numSamples); // Compute a predicted state sample in each iteration for (int i = 0; i < numSamples; ++i) { const double px = stateSamples(0, i); const double py = stateSamples(1, i); const double dir = stateSamples(2, i); const double speed = stateSamples(3, i); const double turnRate = stateSamples(4, i); const double speedNoise = noiseSamples(0, i); const double turnRateNoise = noiseSamples(1, i); const double predSpeed = speed + speedNoise; const double predTurnRate = turnRate + turnRateNoise; const double predDir = dir + deltaT * predTurnRate; predictedStates(0, i) = px + std::cos(predDir) * deltaT * predSpeed; predictedStates(1, i) = py + std::sin(predDir) * deltaT * predSpeed; predictedStates(2, i) = predDir; predictedStates(3, i) = predSpeed; predictedStates(4, i) = predTurnRate; } // Return the computed predicted state samples back to MATLAB outputs[0] = predictedStates; } catch (std::exception& ex) { mexErrMsgTxt(ex.what()); } }
The C++ code is structured as follows.
We first parse the input parameters and check that we get the same number of state samples and noise samples in order to avoid unintended out of bounds memory accesses. Note that we hard coded the dimensions of the system state and the system noise, as we know those in advance. However, we do not know the number of samples passed to the MEX file at compile time. Hence, we accept any number of samples by using the Eigen::Dynamic syntax.
Second, we allocate the output memory based on the passed number of samples.
Third, we perform the actual computation of the predicted state samples. For that, we iterate over all samples using a for loop. In each iteration, we compute a single predicted state sample and store it in the previously allocated memory.
Finally, we return the set of predicted state samples back to MATLAB.
Compile the MEX file with
>> compileMex('targetSystemEquation.cpp')
In order to switch to the new MEX-based system model, we only have to modify a single line in NonlinearEstimationExample.m, that is, we replace the line
sysModel = TargetSysModel();
with
sysModel = TargetSysModelMex();
Another Implementation Using Eigen's Capabilities
For those who are familiar with the Eigen linear algebra library might thought that the computation of the predicted state samples could be done without a for loop by using the matrix methods provided by the Eigen. Indeed, we can replace the for loop in targetSystemEquation.cpp with
// Predicted speed predictedStates.row(3) = stateSamples.row(3) + noiseSamples.row(0); // Predicted turn rate predictedStates.row(4) = stateSamples.row(4) + noiseSamples.row(1); // Predicted direction predictedStates.row(2) = stateSamples.row(2) + deltaT * predictedStates.row(4); // Note that we need the array() cast to perform element-wise cos()/sin() and element-wise multiplication // Predicted x position predictedStates.row(0) = stateSamples.row(0) + deltaT * (predictedStates.row(2).array().cos() * predictedStates.row(3).array()).matrix(); // Predicted y position predictedStates.row(1) = stateSamples.row(1) + deltaT * (predictedStates.row(2).array().sin() * predictedStates.row(3).array()).matrix();
to get the same result. This looks now very similar to the original MATLAB implementation. However, the for loop allows for an easy multithreading as we will see later. Moreover, there might be situations where a for loop is the best way to write a complex system model/measurement model.