System Controlling: December 2012

Friday, December 21, 2012

Discrete H infinity filter design

Short introduction

In many application cases where some of the system states can't be measured, we can use a state observer. One solution would be the Kálmán filter which I already presented. Another solution is the $H_\infty$ filter which is robust regarding the unpredictable noise source. In the Kálmán filter not only the noise process needs to be zero mean, but also the $Q$, $R$ covariance matrices have to be known, without them we can't design an appropriate observer. While in the $H_\infty$ filter doesn't make any assumptions about the noise, it minimizes the worst case estimation error.

Discrete $H_\infty$ filter design

We have the following discrete-time system
$$x_{k+1}=A_k x_k+B_k u_k + w_k\\y_k=C_k x_k+D_k u_k+v_k\tag{1}$$
The $1$ system has to be controllable and observable. We would like to achieve a small estimation error $e_k=z_k - \hat{z}_k$ for any $w_k,v_k$. In this case we want to solve the minimax problem as follows:
$$min_xmax_{w,v}J\tag{2}$$
where $J$ is:
$$J=\frac{\sum\limits_{k=0}^{N-1}{\left|\left|z_k - \hat{z}_k\right|\right|_{Q_k}^2}}{\left|\left|x_0 - \hat{x}_0\right|\right|_{P_0}^2+\sum\limits_{k=0}^{N-1}{\left|\left|w_k\right|\right|_{W_k}^2+\left|\left|v_k\right|\right|_{V_k}^2}}\tag{3}$$
Theorem: Let $\gamma>0$ be a prescribed level of noise attenuation. Then there exists a $H_\infty$ filter for $z_k$ if and only if there exists a stabilizing symmetric solution $P_k>0$ to the following discrete-time Ricatti equation:

 $$P_{k+1}=A_kP_k(I-\gamma Q_kP_k+C_k^TV_k^{-1}C_kP_k)^{-1}A_k^T+B_kW_kB_k^T\tag{4}$$
where
$$\hat{x}_{k+1}=A \hat{x}_k+B u_k+K_k(y_k-C_k\hat{x}_k)\tag{5}$$
The $K_k$ is the gain of the $H_\infty$ filter and it is given by:
$$K_k=A_kP_k(I-\gamma Q_kP_k+C_k^TV_k^{-1}C_kP_k)^{-1}C_k^TV_k^{-1}\tag{6}$$
Proof: Xuemin Shen and Li Deng: Game Theory Approach to Discrete $H_\infty$ Filter Design

As you can see the filter gain doesn’t depend from the states of the system so it's possible to calculate offline.

Cpp implementation

On github you can find a C++ implementation of the $H_\infty$ filter, this is only a chunk of the C++ class.
/*
  *@summary: The filter gain calculation
  *@param A: State space model A matrix
  *@param B: State space mode B matrix
  *@return: Filter Gain
  */
 Matrix HINF::calkGain(Matrix A, Matrix B) {
  if (A.rows() != P.cols())
   ERRORH::throwerror("A rows has to be equal with P columns");

  Matrix I = Matrix::Identity(A.cols(), A.cols());

  Matrix L = (I - lambda * Q * P + C.transpose() * V.inverse() * C * P).inverse();

  P = A * P * L * A.transpose() + B * W * B.transpose();

  K = A * P * L * C.transpose() * V.inverse();

  return K;
 }

 /*
  *@summary: Update the state
  *@param A: State space model A matrix
  *@param B: State space mode B matrix
  *@return: The new state
  */
 Matrix HINF::updateState(Matrix A, Matrix B, Matrix u) {
  x = A * x + B * u;
  return x;
 }

 /*
  *@sumary: Estimate the next states
  *@param M: Measured state
  */
 Matrix HINF::estimate(Matrix M) {

  x = x + K * (M - C * x);

  return x;
 }

Readings:


  • Kemin Zhou: ESSENTIALS OF ROBUST CONTROL
  • Xuemin Shen and Li Deng: Game Theory Approach to Discrete $H_\infty$ Filter Design

Sunday, December 2, 2012

MPC implementation in C++

I was really curious about how fast can we calculate the control signal using MPC. Because of this, first I wanted to write a short C code, but finally I end up with C++. because it is easy to overload the operations and more flexible and easier to implement the Matrix operations. Also there are disadvantages just to mention that the std::vector is slower than the C array and C++ can increase the size of the compiled code significantly.

I have to mention that this is my first code in C++, but I already have experience with OOP(Matlab, Python, Java, PHP) and at the University I got the basics of the C. 

For the first test I used the Eigen library which is really straight forward, but where is the challenge if I'm using a libraries instead of writing my own code and learning new OP language. With the Eigen I could achieve around 6e-5s which is really good, but this package is optimized for big matrices, take a look on the benchmark. In my case, the biggest matrix is a 4x4 matrix, which is really small.

In the second test I wrote my own library(you can fork from this on the github), I'm using arrays instead of std::vectors, because it's faster as I mentioned before.
The result of the test:
Runtime

My next step was to test on my Raspberry Pi using ChibiOS/RT(ARM  CPU). In this platform to calculate one control signal took around 2ms which is not really good. In the mean time I also install Arch Linux on the RPI where the runtime was 0.4ms which is a bit better, so I will try to compile the Linux kernel with real time capability.

Finally this is the test code
int main(int argc, char* argv[]) {

 /*
  * Defining the variables
  */
 //Control horizon
 int nc = 4;
 // Prediction horizon
 int np = 4;
 //Control signal 
 Matrix u(1, 1);
 // Sampling time
 double Ts = 5e-5;

 MPC mySysMat(2, 1, 1);
 //Error matrix, just for testing; 

 Matrix DeltaU(np, 1);

 Matrix R = 1e-5
   * Matrix::Identity(mySysMat.C.rows() * nc,
     mySysMat.C.rows() * nc);
 Matrix Q = 1e4 * Matrix::Identity(np, np);

 Matrix x0(2, 1);
 //Reference signal
 Matrix y_ref(100000, 1);
 y_ref.rblock(0, 0, 50000, 1) = 100 * Matrix::Ones(50000, 1);
 y_ref.rblock(50000, 0, 100000, 1) = -100 * Matrix::Ones(50000, 1);

 double start = now();

 //DC motor parameters
 double Rm = 0.35;
 double Km = 0.0296;
 double Ke = 0.0296;
 double b = 6.7 * 10e-5;
 double J = 2.9 * 10e-6;
 double Fc = 0.02;
 double L = 25 * 10e-6;

 /*
  Parsing the input parameters
  */
 for (int i = 1; i < argc; i++) {
  if (i + 1 != argc) // Check that we haven't finished parsing already
   if (!strcmp(argv[i], "-s")) {
    Ts = atof(argv[i + 1]);
   }
  if (!strcmp(argv[i], "-nc")) {
   nc = atoi(argv[i + 1]);
  }
  if (!strcmp(argv[i], "-np")) {
   np = atoi(argv[i + 1]);
  }

  if (!strcmp(argv[i], "-h")) {
#ifdef CMD
   std::cout << "Coming soon..." << std::endl;
#endif

  }
 }

 //Initializing the system matrices
 mySysMat.Fi << 1 - Ts * (Rm / L), -Ts * (Ke / L), Ts * (Km / J), 1
   - Ts * (b / J);

 mySysMat.Ga << Ts * 1 / L, 0;

 mySysMat.C << 0, 1;

 mySysMat.calcMPCFi(np);
 mySysMat.calcMPCGa(np);
 mySysMat.calcMPCGy(np, nc);
#ifdef DEBUG
 std::cout << "Calculation Fi,Ga,Gy took : " << (double) (now() - start)
   << std::endl;
#endif

 Matrix calcT(1, y_ref.rows());
 Matrix u_hist(1, y_ref.rows());
 Matrix w_hist(1, y_ref.rows());

 for (int i = 0; i < y_ref.rows() - np; i++) {

  start = now();
  //Calculating the error

  DeltaU = mySysMat.calcContSig(
    mySysMat.calcError(y_ref.block(i, 0, i + np, 1), x0, u), Q, R);

  u(0, 0) += DeltaU(0, 0);

  //We are not including this in the time measurement
  if (abs(u(0, 0)) > 5)
   u(0, 0) = sgn(u(0, 0)) * 5;

  u_hist(0, i) = u(0, 0);

  w_hist(0, i) = x0(1, 0);
  //Simulating the system
  x0 = mySysMat.Fi * x0 + mySysMat.Ga * u;

  //Storing the calculation time
  calcT(0, i) = (double) (now() - start);

 }
#ifdef DEBUG
 std::cout << "Minimum:" << calcT.min() << std::endl;
 std::cout << "Maximum:" << calcT.max() << std::endl;
#endif

#ifdef PLOT
 Gnuplot g3("lines");
 plot_x(y_ref, Ts, &g3);
 plot_x(w_hist, Ts, &g3);

 Gnuplot g2("lines");
 plot_x(u_hist, Ts, &g2);

 Gnuplot g1("lines");
 plot_x(calcT, Ts, &g1);
 std::cin.get();
#endif