/**
 ******************************************************************************
 *
 * @file       insgps.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      An INS/GPS algorithm implemented with an EKF.
 *
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "insgps.h"
#include <math.h>
#include <stdint.h>

// constants/macros/typdefs
#define NUMX 13  // number of states, X is the state vector
#define NUMW 9   // number of plant noise inputs, w is disturbance noise vector
#define NUMV 10  // number of measurements, v is the measurement noise vector
#define NUMU 6   // number of deterministic inputs, U is the input vector

// Private functions
void CovariancePrediction(float F[NUMX][NUMX], float G[NUMX][NUMW], float Q[NUMW], float dT, float P[NUMX][NUMX]);
void SerialUpdate(float H[NUMV][NUMX], float R[NUMV], float Z[NUMV], float Y[NUMV],
				  float P[NUMX][NUMX], float X[NUMX], uint16_t SensorsUsed);
void RungeKutta(float X[NUMX],float U[NUMU], float dT);
void StateEq(float X[NUMX],float U[NUMU],float Xdot[NUMX]);
void LinearizeFG(float X[NUMX],float U[NUMU], float F[NUMX][NUMX], float G[NUMX][NUMW]);
void MeasurementEq(float X[NUMX], float Be[3], float Y[NUMV]);
void LinearizeH(float X[NUMX], float Be[3], float H[NUMV][NUMX]);

// Private variables
float F[NUMX][NUMX], G[NUMX][NUMW], H[NUMV][NUMX];  // linearized system matrices
													// global to init to zero and maintain zero elements
float Be[3];                                        // local magnetic unit vector in NED frame
float P[NUMX][NUMX], X[NUMX];                       // covariance matrix and state vector
float Q[NUMW], R[NUMV];                             // input noise and measurement noise variances


//  *************  Exposed Functions ****************
//  *************************************************
void INSGPSInit()   //pretty much just a place holder for now
{
	Be[0]=1; Be[1]=0; Be[2]=0;  // local magnetic unit vector

	P[0][0]=P[1][1]=P[2][2]=25;             // initial position variance (m^2)
	P[3][3]=P[4][4]=P[5][5]=5;              // initial velocity variance (m/s)^2
	P[6][6]=P[7][7]=P[8][8]=P[9][9]=1e-5;   // initial quaternion variance
	P[10][10]=P[11][11]=P[12][12]=1e-5;     // initial gyro bias variance (rad/s)^2

	X[0]=X[1]=X[2]=X[3]=X[4]=X[5]=0;    // initial pos and vel (m)
	X[6]=1; X[7]=X[8]=X[9]=0;           // initial quaternion (level and North) (m/s)
	X[10]=X[11]=X[12]=0;                // initial gyro bias (rad/s)

	Q[0]=Q[1]=Q[2]=50e-8;           // gyro noise variance (rad/s)^2
	Q[3]=Q[4]=Q[5]=0.01;            // accelerometer noise variance (m/s^2)^2
	Q[6]=Q[7]=Q[8]=2e-7;            // gyro bias random walk variance (rad/s^2)^2

	R[0]=R[1]=0.004;       // High freq GPS horizontal position noise variance (m^2)
	R[2]=0.036;            // High freq GPS vertical position noise variance (m^2)
	R[3]=R[4]=0.004;       // High freq GPS horizontal velocity noise variance (m/s)^2
	R[5]=0;                // High freq GPS vertical velocity noise variance (m/s)^2
	R[6]=R[7]=R[8]=0.005;  // magnetometer unit vector noise variance
	R[9]=1;                // High freq altimeter noise variance (m^2)
}

void INSSetGyroBias(float gyro_bias[3]) 
{
  X[10] = gyro_bias[0];
  X[11] = gyro_bias[1];
  X[12] = gyro_bias[2];
}

void INSSetAccelVar(float accel_var[3]) 
{
  Q[3] = accel_var[0];
  Q[4] = accel_var[1];
  Q[5] = accel_var[2];
}

void INSSetGyroVar(float gyro_var[3])
{
  Q[0] = gyro_var[0];
  Q[1] = gyro_var[1];
  Q[2] = gyro_var[2];
}

void INSSetMagVar(float scaled_mag_var[3]) 
{
  R[6] = scaled_mag_var[0];
  R[7] = scaled_mag_var[1];
  R[8] = scaled_mag_var[2];
}

void INSSetMagNorth(float B[3])
{
  Be[0] = B[0];
  Be[1] = B[1];
  Be[2] = B[2];
}

void INSPrediction(float gyro_data[3], float accel_data[3], float dT)
{
	float U[6];
	float qmag;

	// rate gyro inputs in units of rad/s
	U[0]=gyro_data[0];
	U[1]=gyro_data[1];
	U[2]=gyro_data[2];

	// accelerometer inputs in units of m/s
	U[3]=accel_data[0];
	U[4]=accel_data[1];
	U[5]=accel_data[2];

    // EKF prediction step
	LinearizeFG(X,U,F,G);
    RungeKutta(X,U,dT);
    qmag=sqrt(X[6]*X[6] + X[7]*X[7] + X[8]*X[8] + X[9]*X[9]);
    X[6] /= qmag; X[7] /= qmag; X[8] /= qmag; X[9] /= qmag;
    CovariancePrediction(F,G,Q,dT,P);

    // Update Nav solution structure
    Nav.Pos[0] = X[0];
    Nav.Pos[1] = X[1];
    Nav.Pos[2] = X[2];
    Nav.Vel[0] = X[3];
    Nav.Vel[1] = X[4];
    Nav.Vel[2] = X[5];
    Nav.q[0] = X[6];
    Nav.q[1] = X[7];
    Nav.q[2] = X[8];
    Nav.q[3] = X[9];
}

void MagCorrection(float mag_data[3])
{
    float Z[10], Y[10];
    float Bmag, qmag;

	// magnetometer data in any units (use unit vector) and in body frame
	Bmag = sqrt(mag_data[0]*mag_data[0] + mag_data[1]*mag_data[1] + mag_data[2]*mag_data[2]);
	Z[6] = mag_data[0]/Bmag;
	Z[7] = mag_data[1]/Bmag;
	Z[8] = mag_data[2]/Bmag;

    // EKF correction step
	LinearizeH(X,Be,H);
	MeasurementEq(X,Be,Y);
	SerialUpdate(H,R,Z,Y,P,X,MagSensors);
	qmag=sqrt(X[6]*X[6] + X[7]*X[7] + X[8]*X[8] + X[9]*X[9]);
	X[6] /= qmag; X[7] /= qmag; X[8] /= qmag; X[9] /= qmag;

    // Update Nav solution structure
    Nav.Pos[0] = X[0];
    Nav.Pos[1] = X[1];
    Nav.Pos[2] = X[2];
    Nav.Vel[0] = X[3];
    Nav.Vel[1] = X[4];
    Nav.Vel[2] = X[5];
    Nav.q[0] = X[6];
    Nav.q[1] = X[7];
    Nav.q[2] = X[8];
    Nav.q[3] = X[9];
}

void FullCorrection(float mag_data[3], float Pos[3], float Vel[3], float BaroAlt)
{
    float Z[10], Y[10];
    float Bmag, qmag;

	// GPS Position in meters and in local NED frame
	Z[0]=Pos[0];
	Z[1]=Pos[1];
	Z[2]=Pos[2];

	// GPS Velocity in meters and in local NED frame
	Z[3]=Vel[0];
	Z[4]=Vel[1];
	Z[5]=Vel[2];

	// magnetometer data in any units (use unit vector) and in body frame
	Bmag = sqrt(mag_data[0]*mag_data[0] + mag_data[1]*mag_data[1] + mag_data[2]*mag_data[2]);
	Z[6] = mag_data[0]/Bmag;
	Z[7] = mag_data[1]/Bmag;
	Z[8] = mag_data[2]/Bmag;

	// barometric altimeter in meters and in local NED frame
	Z[9] = BaroAlt;

	// EKF correction step
	LinearizeH(X,Be,H);
	MeasurementEq(X,Be,Y);
	SerialUpdate(H,R,Z,Y,P,X,FullSensors);
	qmag=sqrt(X[6]*X[6] + X[7]*X[7] + X[8]*X[8] + X[9]*X[9]);
	X[6] /= qmag; X[7] /= qmag; X[8] /= qmag; X[9] /= qmag;

    // Update Nav solution structure
    Nav.Pos[0] = X[0];
    Nav.Pos[1] = X[1];
    Nav.Pos[2] = X[2];
    Nav.Vel[0] = X[3];
    Nav.Vel[1] = X[4];
    Nav.Vel[2] = X[5];
    Nav.q[0] = X[6];
    Nav.q[1] = X[7];
    Nav.q[2] = X[8];
    Nav.q[3] = X[9];
}

void GndSpeedAndMagCorrection(float Speed, float Heading, float mag_data[3])
{
    float Z[10], Y[10];
    float Bmag, qmag;

	// Ground Speed in m/s and Heading in rad
    Z[3] = Speed*cos((double)Heading);
    Z[4] = Speed*sin((double)Heading);

    // magnetometer data in any units (use unit vector) and in body frame
	Bmag = sqrt(mag_data[0]*mag_data[0] + mag_data[1]*mag_data[1] + mag_data[2]*mag_data[2]);
	Z[6] = mag_data[0]/Bmag;
	Z[7] = mag_data[1]/Bmag;
	Z[8] = mag_data[2]/Bmag;

    // EKF correction step
	LinearizeH(X,Be,H);
	MeasurementEq(X,Be,Y);
	SerialUpdate(H,R,Z,Y,P,X,GndSpeedAndMagSensors);
	qmag=sqrt(X[6]*X[6] + X[7]*X[7] + X[8]*X[8] + X[9]*X[9]);
	X[6] /= qmag; X[7] /= qmag; X[8] /= qmag; X[9] /= qmag;

    // Update Nav solution structure
    Nav.Pos[0] = X[0];
    Nav.Pos[1] = X[1];
    Nav.Pos[2] = X[2];
    Nav.Vel[0] = X[3];
    Nav.Vel[1] = X[4];
    Nav.Vel[2] = X[5];
    Nav.q[0] = X[6];
    Nav.q[1] = X[7];
    Nav.q[2] = X[8];
    Nav.q[3] = X[9];
}

//  *************  CovariancePrediction *************
//  Does the prediction step of the Kalman filter for the covariance matrix
//  Output, Pnew, overwrites P, the input covariance
//  Pnew = (I+F*T)*P*(I+F*T)' + T^2*G*Q*G'
//  Q is the discrete time covariance of process noise
//  Q is vector of the diagonal for a square matrix with
//    dimensions equal to the number of disturbance noise variables
//  Could be much more efficient using the sparse, block structure of F and G
//  ************************************************

void CovariancePrediction(float F[NUMX][NUMX], float G[NUMX][NUMW], float Q[NUMW], float dT, float P[NUMX][NUMX]){
 float Dummy[NUMX][NUMX], dTsq;
 uint8_t i,j,k;

 //  Pnew = (I+F*T)*P*(I+F*T)' + T^2*G*Q*G' = T^2[(P/T + F*P)*(I/T + F') + G*Q*G')]

   dTsq = dT*dT;

   for (i=0;i<NUMX;i++)                   // Calculate Dummy = (P/T +F*P)
    for (j=0;j<NUMX;j++){
      Dummy[i][j] = P[i][j]/dT;
      for (k=0;k<NUMX;k++) Dummy[i][j] += F[i][k]*P[k][j];
    }
   for (i=0;i<NUMX;i++)                  // Calculate Pnew = Dummy/T + Dummy*F' + G*Qw*G'
    for (j=i;j<NUMX;j++){                // Use symmetry, ie only find upper triangular
      P[i][j] = Dummy[i][j]/dT;
      for (k=0;k<NUMX;k++) P[i][j] += Dummy[i][k]*F[j][k];   // P = Dummy/T + Dummy*F'
      for (k=0;k<NUMW;k++) P[i][j] += Q[k]*G[i][k]*G[j][k];  // P = Dummy/T + Dummy*F' + G*Q*G'
      P[j][i] = P[i][j] = P[i][j]*dTsq;                      // Pnew = T^2*P and fill in lower triangular;
    }
}

//  *************  SerialUpdate *******************
//  Does the update step of the Kalman filter for the covariance and estimate
//  Outputs are Xnew & Pnew, and are written over P and X
//  Z is actual measurement, Y is predicted measurement
//  Xnew = X + K*(Z-Y), Pnew=(I-K*H)*P,
//    where K=P*H'*inv[H*P*H'+R]
//  NOTE the algorithm assumes R (measurement covariance matrix) is diagonal
//    i.e. the measurment noises are uncorrelated.
//  It therefore uses a serial update that requires no matrix inversion by
//    processing the measurements one at a time.
//  Algorithm - see Grewal and Andrews, "Kalman Filtering,2nd Ed" p.121 & p.253
//            - or see Simon, "Optimal State Estimation," 1st Ed, p.150
//  The SensorsUsed variable is a bitwise mask indicating which sensors
//     should be used in the update.
//  ************************************************

void SerialUpdate(float H[NUMV][NUMX], float R[NUMV], float Z[NUMV], float Y[NUMV],
				  float P[NUMX][NUMX], float X[NUMX], uint16_t SensorsUsed){
 float HP[NUMX], K[NUMX], HPHR, Error;
 uint8_t i,j,k,m;

  for (m=0;m<NUMV;m++){

	  if ( SensorsUsed & (0x01<<m)){        // use this sensor for update

			for (j=0;j<NUMX;j++){                   // Find Hp = H*P
			  HP[j]=0;
			  for (k=0;k<NUMX;k++) HP[j] += H[m][k]*P[k][j];
			}
			HPHR = R[m];                            // Find  HPHR = H*P*H' + R
			for (k=0;k<NUMX;k++) HPHR += HP[k]*H[m][k];

			for (k=0;k<NUMX;k++) K[k] = HP[k]/HPHR; // find K = HP/HPHR

			for (i=0;i<NUMX;i++){                   // Find P(m)= P(m-1) + K*HP
			  for (j=i;j<NUMX;j++) P[i][j]=P[j][i] = P[i][j] - K[i]*HP[j];
			}

			Error = Z[m]-Y[m];
			for (i=0;i<NUMX;i++)               // Find X(m)= X(m-1) + K*Error
			  X[i] = X[i] + K[i]*Error;

	  }
  }
}

//  *************  RungeKutta **********************
//  Does a 4th order Runge Kutta numerical integration step
//  Output, Xnew, is written over X
//  NOTE the algorithm assumes time invariant state equations and
//    constant inputs over integration step
//  ************************************************

void RungeKutta(float X[NUMX],float U[NUMU], float dT){

 float dT2=dT/2, K1[NUMX], K2[NUMX], K3[NUMX], K4[NUMX], Xlast[NUMX];
 uint8_t i;

  for (i=0;i<NUMX;i++) Xlast[i] = X[i];        // make a working copy

  StateEq(X,U,K1);                                     // k1 = f(x,u)
  for (i=0;i<NUMX;i++) X[i] = Xlast[i] + dT2*K1[i];
  StateEq(X,U,K2);                                     // k2 = f(x+0.5*dT*k1,u)
  for (i=0;i<NUMX;i++) X[i] = Xlast[i] + dT2*K2[i];
  StateEq(X,U,K3);                                     // k3 = f(x+0.5*dT*k2,u)
  for (i=0;i<NUMX;i++) X[i] = Xlast[i] + dT*K3[i];
  StateEq(X,U,K4);                                     // k4 = f(x+dT*k3,u)

  // Xnew  = X + dT*(k1+2*k2+2*k3+k4)/6
  for (i=0;i<NUMX;i++) X[i] = Xlast[i] + dT*(K1[i]+2*K2[i]+2*K3[i]+K4[i])/6;
}

//  *************  Model Specific Stuff  ***************************
//  ***  StateEq, MeasurementEq, LinerizeFG, and LinearizeH ********
//
//  State Variables = [Pos Vel Quaternion GyroBias NO-AccelBias]
//  Deterministic Inputs = [AngularVel Accel]
//  Disturbance Noise = [GyroNoise AccelNoise GyroRandomWalkNoise NO-AccelRandomWalkNoise]
//
//  Measurement Variables = [Pos Vel BodyFrameMagField Altimeter]
//  Inputs to Measurement = [EarthFrameMagField]
//
//  Notes: Pos and Vel in earth frame
//  AngularVel and Accel in body frame
//  MagFields are unit vectors
//  Xdot is output of StateEq()
//  F and G are outputs of LinearizeFG(), all elements not set should be zero
//  y is output of OutputEq()
//  H is output of LinearizeH(), all elements not set should be zero
//  ************************************************

void StateEq(float X[NUMX],float U[NUMU],float Xdot[NUMX]){
  float ax, ay, az, wx, wy, wz, q0, q1, q2, q3;

  // ax=U[3]-X[13]; ay=U[4]-X[14]; az=U[5]-X[15];  // subtract the biases on accels
  ax=U[3]; ay=U[4]; az=U[5];                    // NO BIAS STATES ON ACCELS
  wx=U[0]-X[10]; wy=U[1]-X[11]; wz=U[2]-X[12];  // subtract the biases on gyros
  q0=X[6]; q1=X[7]; q2=X[8]; q3=X[9];

  // Pdot = V
  Xdot[0]=X[3];  Xdot[1]=X[4]; Xdot[2]=X[5];

  // Vdot = Reb*a
  Xdot[3]=(q0*q0+q1*q1-q2*q2-q3*q3)*ax + 2*(q1*q2-q0*q3)*ay + 2*(q1*q3+q0*q2)*az;
  Xdot[4]=2*(q1*q2+q0*q3)*ax + (q0*q0-q1*q1+q2*q2-q3*q3)*ay + 2*(q2*q3-q0*q1)*az;
  Xdot[5]=2*(q1*q3-q0*q2)*ax + 2*(q2*q3+q0*q1)*ay + (q0*q0-q1*q1-q2*q2+q3*q3)*az + 9.81;

  // qdot = Q*w
  Xdot[6] = (-q1*wx-q2*wy-q3*wz)/2;
  Xdot[7] = (q0*wx-q3*wy+q2*wz)/2;
  Xdot[8] = (q3*wx+q0*wy-q1*wz)/2;
  Xdot[9] = (-q2*wx+q1*wy+q0*wz)/2;

  // best guess is that bias stays constant
  Xdot[10]=Xdot[11]=Xdot[12]=0;
}

void LinearizeFG(float X[NUMX],float U[NUMU], float F[NUMX][NUMX], float G[NUMX][NUMW]){
  float ax, ay, az, wx, wy, wz, q0, q1, q2, q3;

  // ax=U[3]-X[13]; ay=U[4]-X[14]; az=U[5]-X[15];  // subtract the biases on accels
  ax=U[3]; ay=U[4]; az=U[5];                    // NO BIAS STATES ON ACCELS
  wx=U[0]-X[10]; wy=U[1]-X[11]; wz=U[2]-X[12];  // subtract the biases on gyros
  q0=X[6]; q1=X[7]; q2=X[8]; q3=X[9];

  // Pdot = V
  F[0][3]=F[1][4]=F[2][5]=1;

  // dVdot/dq
  F[3][6]=2*(q0*ax-q3*ay+q2*az);   F[3][7]=2*(q1*ax+q2*ay+q3*az);   F[3][8]=2*(-q2*ax+q1*ay+q0*az);  F[3][9]=2*(-q3*ax-q0*ay+q1*az);
  F[4][6]=2*(q3*ax+q0*ay-q1*az);   F[4][7]=2*(q2*ax-q1*ay-q0*az);   F[4][8]=2*(q1*ax+q2*ay+q3*az);   F[4][9]=2*(q0*ax-q3*ay+q2*az);
  F[5][6]=2*(-q2*ax+q1*ay+q0*az);  F[5][7]=2*(q3*ax+q0*ay-q1*az);   F[5][8]=2*(-q0*ax+q3*ay-q2*az);  F[5][9]=2*(q1*ax+q2*ay+q3*az);

  // dVdot/dabias & dVdot/dna  - NO BIAS STATES ON ACCELS - S0 REPEAT FOR G BELOW
  // F[3][13]=G[3][3]=-q0*q0-q1*q1+q2*q2+q3*q3; F[3][14]=G[3][4]=2*(-q1*q2+q0*q3);         F[3][15]=G[3][5]=-2*(q1*q3+q0*q2);
  // F[4][13]=G[4][3]=-2*(q1*q2+q0*q3);         F[4][14]=G[4][4]=-q0*q0+q1*q1-q2*q2+q3*q3; F[4][15]=G[4][5]=2*(-q2*q3+q0*q1);
  // F[5][13]=G[5][3]=2*(-q1*q3+q0*q2);         F[5][14]=G[5][4]=-2*(q2*q3+q0*q1);         F[5][15]=G[5][5]=-q0*q0+q1*q1+q2*q2-q3*q3;

  // dqdot/dq
  F[6][6]=0;     F[6][7]=-wx/2; F[6][8]=-wy/2; F[6][9]=-wz/2;
  F[7][6]=wx/2;  F[7][7]=0;     F[7][8]=wz/2;  F[7][9]=-wy/2;
  F[8][6]=wy/2;  F[8][7]=-wz/2; F[8][8]=0;     F[8][9]=wx/2;
  F[9][6]=wz/2;  F[9][7]=wy/2;  F[9][8]=-wx/2; F[9][9]=0;

  // dqdot/dwbias
  F[6][10]=q1/2;  F[6][11]=q2/2;  F[6][12]=q3/2;
  F[7][10]=-q0/2; F[7][11]=q3/2;  F[7][12]=-q2/2;
  F[8][10]=-q3/2; F[8][11]=-q0/2; F[8][12]=q1/2;
  F[9][10]=q2/2;  F[9][11]=-q1/2; F[9][12]=-q0/2;

  // dVdot/dna  - NO BIAS STATES ON ACCELS - S0 REPEAT FOR G HERE
  G[3][3]=-q0*q0-q1*q1+q2*q2+q3*q3; G[3][4]=2*(-q1*q2+q0*q3);         G[3][5]=-2*(q1*q3+q0*q2);
  G[4][3]=-2*(q1*q2+q0*q3);         G[4][4]=-q0*q0+q1*q1-q2*q2+q3*q3; G[4][5]=2*(-q2*q3+q0*q1);
  G[5][3]=2*(-q1*q3+q0*q2);         G[5][4]=-2*(q2*q3+q0*q1);         G[5][5]=-q0*q0+q1*q1+q2*q2-q3*q3;

  // dqdot/dnw
  G[6][0]=q1/2;  G[6][1]=q2/2;  G[6][2]=q3/2;
  G[7][0]=-q0/2; G[7][1]=q3/2;  G[7][2]=-q2/2;
  G[8][0]=-q3/2; G[8][1]=-q0/2; G[8][2]=q1/2;
  G[9][0]=q2/2;  G[9][1]=-q1/2; G[9][2]=-q0/2;

  // dwbias = random walk noise
  G[10][6]=G[11][7]=G[12][8]=1;
  // dabias = random walk noise
  // G[13][9]=G[14][10]=G[15][11]=1;  // NO BIAS STATES ON ACCELS
}

void MeasurementEq(float X[NUMX], float Be[3], float Y[NUMV]){
  float q0, q1, q2, q3;

  q0=X[6]; q1=X[7]; q2=X[8]; q3=X[9];

  // first six outputs are P and V
  Y[0]=X[0]; Y[1]=X[1]; Y[2]=X[2]; Y[3]=X[3]; Y[4]=X[4]; Y[5]=X[5];

  // Bb=Rbe*Be
  Y[6]=(q0*q0+q1*q1-q2*q2-q3*q3)*Be[0] + 2*(q1*q2+q0*q3)*Be[1] + 2*(q1*q3-q0*q2)*Be[2];
  Y[7]=2*(q1*q2-q0*q3)*Be[0] + (q0*q0-q1*q1+q2*q2-q3*q3)*Be[1] + 2*(q2*q3+q0*q1)*Be[2];
  Y[8]=2*(q1*q3+q0*q2)*Be[0] + 2*(q2*q3-q0*q1)*Be[1] + (q0*q0-q1*q1-q2*q2+q3*q3)*Be[2];

  // Alt = -Pz
  Y[9] = -X[2];
}

void LinearizeH(float X[NUMX], float Be[3], float H[NUMV][NUMX]){
  float q0, q1, q2, q3;

  q0=X[6]; q1=X[7]; q2=X[8]; q3=X[9];

  // dP/dP=I;
  H[0][0]=H[1][1]=H[2][2]=1;
  // dV/dV=I;
  H[3][3]=H[4][4]=H[5][5]=1;

  // dBb/dq
  H[6][6]=2*(q0*Be[0]+q3*Be[1]-q2*Be[2]);  H[6][7]=2*(q1*Be[0]+q2*Be[1]+q3*Be[2]); H[6][8]=2*(-q2*Be[0]+q1*Be[1]-q0*Be[2]); H[6][9]=2*(-q3*Be[0]+q0*Be[1]+q1*Be[2]);
  H[7][6]=2*(-q3*Be[0]+q0*Be[1]+q1*Be[2]); H[7][7]=2*(q2*Be[0]-q1*Be[1]+q0*Be[2]); H[7][8]=2*(q1*Be[0]+q2*Be[1]+q3*Be[2]);  H[7][9]=2*(-q0*Be[0]-q3*Be[1]+q2*Be[2]);
  H[8][6]=2*(q2*Be[0]-q1*Be[1]+q0*Be[2]);  H[8][7]=2*(q3*Be[0]-q0*Be[1]-q1*Be[2]); H[8][8]=2*(q0*Be[0]+q3*Be[1]-q2*Be[2]);  H[8][9]=2*(q1*Be[0]+q2*Be[1]+q3*Be[2]);

  // dAlt/dPz = -1
  H[9][2]=-1;
}