Guillaume Frèche

Personal website

Home About Machine learning Signal processing Maths

C++ files for Kalman filters

main.cpp

//
//  main.cpp
//  Kalman_filter
//
//  Created by Guillaume Frèche on 30/09/2019.
//  Copyright © 2019 Guillaume Frèche. All rights reserved.
//

#include <iostream>
//#include <exception>
#include <random>
#include <chrono>

#include "KalmanFilter.hpp"

int main(int argc, const char * argv[]) {
    using namespace std::chrono;
    auto start = high_resolution_clock::now();
    
    // Time step
    double dt = 1.;
    
    // Kalman filter's model matrices
    // Matrix F
    vector<vector<double>> matF = { {1, dt}, {0, 1} };
    // Matrix Rv
    double sigma_a = 0.2;
    vector<vector<double>> matRv = { {sigma_a*sigma_a*pow(dt, 4.)/4, sigma_a*sigma_a*pow(dt, 3.)/2},
        {sigma_a*sigma_a*pow(dt, 3.)/2, dt*dt*sigma_a*sigma_a} };
    // Matrix H
    vector<vector<double>> matH = { {1, 0} };
    // Matrix Ru
    double sigma_noise = 30;
    vector<vector<double>> matRu = { {sigma_noise*sigma_noise} };
    
    // Construct Kalman filter
    KalmanFilter filter = KalmanFilter(2,1, Matrix(matF), Matrix(matRv), Matrix(matH), Matrix(matRu));
    
    int niter = 500;
    
    // Generate random acceleration
    random_device rd;
    mt19937 generator(rd());
    normal_distribution<double> dist_accel(0, sigma_a);
    vector<double> accel;
    for (int i=0; i<niter; i++)
        accel.push_back(dist_accel(generator));
    
    // Generate true position and speed
    vector<double> true_pos = {0};
    double sigma_speed = 2;
    normal_distribution<double> dist_speed(0, sigma_speed);
    vector<double> true_speed = {dist_speed(generator)};
    for (int i=0; i<niter; i++){
        true_pos.push_back((*true_pos.end())+((*true_speed.end())*dt)+(accel[i]*dt*dt/2));
        true_speed.push_back((*true_speed.end())+(accel[i]*dt));
    }
    
    // Generate noisy observations
    normal_distribution<double> dist_obs(0, sigma_noise);
    vector<double> obs_pos;
    for (int i=0; i<true_pos.size(); i++){
        obs_pos.push_back(true_pos[i]+dist_obs(generator));
    }
    
    // Apply Kalman filter
    Matrix estim_state = filter.estimate_state(obs_pos);
    
    auto stop = high_resolution_clock::now();
    auto duration = duration_cast<microseconds>(stop - start);
    cout << "Program duration: " << ((double) duration.count())/double(1000000) << " sec " << endl;
    
    return 0;
}

Matrix.hpp

//
//  Matrix.hpp
//  Kalman_filter
//
//  Created by Guillaume Frèche on 30/09/2019.
//  Copyright © 2019 Guillaume Frèche. All rights reserved.
//

#ifndef Matrix_hpp
#define Matrix_hpp

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

class Matrix{
private:
    vector<vector<double>> entries;
    int nb_row;
    int nb_col;
    
    void remove_row(int i);
    void remove_col(int j);
    // Sub matrix obtained by removing row i and column j
    Matrix sub_matrix(int i, int j) const;
    
public:
    // Empty constructor
    Matrix();
    // Copy constructor
    Matrix(const Matrix& mat);
    // Constructor converting a vector of vector into a matrix
    Matrix(const vector<vector<double>>& array);
    
    // Instantiate an all zero matrix
    static Matrix zeros(const int m, const int n);
    // Instantiate identity matrix
    static Matrix identity(const int m);
    
    // Display matrix entries
    void print();
    
    // Operators overload
    Matrix operator+(const Matrix& mat);
    Matrix operator-(const Matrix& mat);
    Matrix operator*(const Matrix& mat);
    Matrix operator*(const double val);
    Matrix operator/(const Matrix& mat);
    Matrix operator/(const double val);
    
    // Transpose matrix
    Matrix transpose() const;
    // Inverse matrix
    Matrix inverse();
    
    // Determinant
    double determinant() const;
    
    // Convert a 1x1 matrix into a double
    double to_double();

    vector<double> get_row(int i);
    vector<double> get_column(int j);
    void set_column(int j, vector<double> col);
};

#endif /* Matrix_hpp */

Matrix.cpp

//
//  Matrix.cpp
//  Kalman_filter
//
//  Created by Guillaume Frèche on 30/09/2019.
//  Copyright © 2019 Guillaume Frèche. All rights reserved.
//

#include "Matrix.hpp"

// Empty constructor
Matrix::Matrix(){
    entries = {};
    nb_row = 0;
    nb_col = 0;
}

// Copy constructor
Matrix::Matrix(const Matrix& mat){
    entries = mat.entries;
    nb_row = mat.nb_row;
    nb_col = mat.nb_col;
}

// Constructor converting a vector of vector into a matrix
Matrix::Matrix(const vector<vector<double>>& array){
    entries = array;
    nb_row = (int) array.size();
    nb_col = (int) array[0].size();
}

// Instantiate an all zero matrix
Matrix Matrix::zeros(const int m, const int n){
    Matrix res;
    if((m>0)&&(n>0)){
        res.entries = vector<vector<double>>(m, vector<double>(n, 0));
        res.nb_row = m;
        res.nb_col = n;
    }
    else{
        throw invalid_argument ("Arguments must be a positive integer.");
    }
    return res;
}

// Instantiate identity matrix
Matrix Matrix::identity(const int m){
    Matrix res;
    if (m>0){
        res = zeros(m,m);
        for (int i=0; i<m; i++){
            res.entries[i][i] = 1;
        }
    }
    else{
        throw invalid_argument ("Argument must be a positive integer.");
    }
    return res;
}

// Display matrix entries
void Matrix::print(){
    for (int i=0; i < nb_row; i++){
        for (int j=0; j < nb_col; j++){
            cout << entries[i][j] << " ";
        }
        cout << endl;
    }
}

Matrix Matrix::operator+(const Matrix& mat){
    Matrix res;
    if ((nb_row!=mat.nb_row)||(nb_col!=mat.nb_col)){
        throw invalid_argument ( "Matrices should have the same dimensions." );
    }
    else{
        res = Matrix::zeros(nb_row, nb_col);
        for (int i=0; i< nb_row; i++){
            for (int j=0; j< nb_col; j++){
                res.entries[i][j] = entries[i][j] + mat.entries[i][j];
            }
        }
    }
    return res;
}

Matrix Matrix::operator-(const Matrix& mat){
    Matrix res;
    if ((nb_row!=mat.nb_row)||(nb_col!=mat.nb_col)){
        throw invalid_argument ( "Matrices should have the same dimensions " );
    }
    else{
        res = Matrix::zeros(nb_row, nb_col);
        for (int i=0; i< nb_row; i++){
            for (int j=0; j< nb_col; j++){
                res.entries[i][j] = entries[i][j] - mat.entries[i][j];
            }
        }
    }
    return res;
}

Matrix Matrix::operator*(const Matrix& mat){
    Matrix res;
    //pair<int, int> curr_shape = get_shape();
    //pair<int, int> mat_shape = mat.get_shape();
    
    if (nb_col!=mat.nb_row){
        throw invalid_argument ( "Matrices should have the same common dimension " );
    }
    else{
        res = Matrix::zeros(nb_row, mat.nb_col);
        for (int i=0; i< nb_row; i++){
            for (int j=0; j< mat.nb_col; j++){
                double temp = 0;
                for (int k=0; k < nb_col; k++){
                    temp += entries[i][k]*mat.entries[k][j];
                }
                res.entries[i][j] = temp;
            }
        }
    }
    return res;
}

Matrix Matrix::operator*(const double val){
    Matrix res = Matrix(entries);
    for (int i=0; i< nb_row; i++){
        for (int j=0; j< nb_col; j++){
            res.entries[i][j] = entries[i][j]*val;
        }
    }
    return res;
}

Matrix Matrix::operator/(const Matrix& mat){
    Matrix res;
    if ((nb_row!=mat.nb_row)||(nb_col!=mat.nb_col)){
        throw invalid_argument ( "Matrices should have the same dimensions " );
    }
    else{
        res = Matrix::zeros(nb_row, nb_col);
        Matrix mat_transp = mat.transpose();
        double det_mat = mat.determinant();
        for (int i=0; i < nb_row; i++){
            for (int j=0; j< nb_col; j++){
                Matrix temp_mat = Matrix(mat_transp);
                temp_mat.set_column(j, get_row(i));
                res.entries[i][j] = temp_mat.determinant()/det_mat;
            }
        }
    }
    return res;
}

Matrix Matrix::operator/(const double val){
    Matrix res = Matrix(entries);
    //pair<int,int> res_shape = res.get_shape();
    for (int i=0; i<nb_row; i++){
        for (int j=0; j<nb_col; j++){
            res.entries[i][j] = entries[i][j] / val;
            //res.set_entry(i, j, res.get_entry(i, j)/val);
        }
    }
    return res;
}

Matrix Matrix::transpose() const{
    Matrix res = Matrix::zeros(nb_col, nb_row);
    for(int i=0; i< nb_col; i++){
        for(int j=0; j<nb_row; j++){
            res.entries[i][j] = entries[j][i];
        }
    }
    return res;
}

Matrix Matrix::inverse(){
    //pair<int,int> curr_shape = get_shape();
    return Matrix::identity(nb_row)/Matrix(entries);
}

double Matrix::determinant() const{
    double res;
    if (nb_row!=nb_col){
        throw invalid_argument ( " Determinant only works for square matrices " );
    }
    else{
        if (nb_row==1){
            res = entries[0][0];
        }
        else{
            res = 0;
            for (int i=0; i<nb_row; i++){
                Matrix sub_mat = sub_matrix(i, 0);
                res += pow(-1, (double) i) * entries[i][0] * (sub_mat.determinant());
            }
        }
    }
    return res;
}

// Convert a 1x1 matrix into a double
double Matrix::to_double(){
    int res;
    if ((nb_row!=1)||(nb_col!=1)){
        throw invalid_argument ( "Matrix must be of shape 1x1" );
    }
    else{
        res = entries[0][0];
    }
    return res;
}

void Matrix::remove_row(int i){
    entries.erase(entries.begin()+i);
}

void Matrix::remove_col(int j){
    for (int i=0; i < entries.size();i++){
        entries[i].erase(entries[i].begin()+j);
    }
}

Matrix Matrix::sub_matrix(int i, int j) const{
    Matrix res = Matrix(entries);
    res.remove_row(i);
    res.remove_col(j);
    res.nb_row--;
    res.nb_col--;
    return res;
}

vector<double> Matrix::get_row(int i){
    return entries[i];
}

vector<double> Matrix::get_column(int j){
    vector<double> res;
    for (int i=0; i< nb_row; i++)
        res.push_back(entries[i][j]);
    return res;
}

void Matrix::set_column(int j, vector<double> col){
    for (int i=0; i<nb_row; i++)
        entries[i][j] = col[i];
}

KalmanFilter.hpp

//
//  KalmanFilter.hpp
//  Kalman_filter
//
//  Created by Guillaume Frèche on 30/09/2019.
//  Copyright © 2019 Guillaume Frèche. All rights reserved.
//

#ifndef KalmanFilter_hpp
#define KalmanFilter_hpp

#include <iostream>
#include "Matrix.hpp"

class KalmanFilter{
private:
    int state_dim;
    int obs_dim;
    Matrix F;
    Matrix Rv;
    Matrix H;
    Matrix Ru;
    
public:
    // Kalman filter constructor
    KalmanFilter(int m, int n, Matrix matF, Matrix matRv, Matrix matH, Matrix matRu) :
    state_dim(m), obs_dim(n), F(matF), Rv(matRv), H(matH), Ru(matRu) {}
    // Kalman filter state estimator
    Matrix estimate_state(vector<double> observ);
};

#endif /* KalmanFilter_hpp */

KalmanFilter.cpp

//
//  KalmanFilter.cpp
//  Kalman_filter
//
//  Created by Guillaume Frèche on 30/09/2019.
//  Copyright © 2019 Guillaume Frèche. All rights reserved.
//

#include "KalmanFilter.hpp"

Matrix KalmanFilter::estimate_state(vector<double> observ){
    Matrix estim_state = Matrix::zeros(state_dim, (int) observ.size());
    Matrix prior_innov_cov;
    Matrix posterior_innov_cov = Matrix::zeros(state_dim, state_dim);
    Matrix kalman_gain;
    Matrix prior_estim_state;
    Matrix posterior_estim_state;
    
    
    for (int k=1; k<observ.size(); k++){
        // Prior innovation covariance matrix update
        prior_innov_cov = (F*posterior_innov_cov*(F.transpose())) + Rv;
        // Kalman gain update
        double denom = ((H*prior_innov_cov*(H.transpose()))+Ru).to_double();
        kalman_gain = (prior_innov_cov*(H.transpose()))/denom;
        // posterior innovation covariance matrix update
        posterior_innov_cov = (Matrix::identity(state_dim)-(kalman_gain*H))*prior_innov_cov;
        
        // Previous posterior state estimate
        vector<vector<double>> temp_state;
        temp_state.push_back(estim_state.get_column(k));
        posterior_estim_state = Matrix(temp_state).transpose();
        // New prior state estimate
        prior_estim_state = F*posterior_estim_state;
        // New posterior state estimate
        posterior_estim_state = prior_estim_state + (kalman_gain*(observ[k]-((H*prior_estim_state).to_double())));
        
        // Store the new state estimate
        estim_state.set_column(k, posterior_estim_state.get_column(0));
    }
    
    return estim_state;
}