MotionCal/magcal.c

619 lines
21 KiB
C

// Copyright (c) 2014, Freescale Semiconductor, Inc.
// All rights reserved.
// vim: set ts=4:
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of Freescale Semiconductor, Inc. nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL FREESCALE SEMICONDUCTOR, INC. BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// This file contains magnetic calibration functions. It is STRONGLY RECOMMENDED
// that the casual developer NOT TOUCH THIS FILE. The mathematics behind this file
// is extremely complex, and it will be very easy (almost inevitable) that you screw
// it up.
//
// Haha - This file has been edited! Please do not blame or pester NXP (formerly
// Freescale) about the "almost inevitable" issues!
#include "imuread.h"
#define FXOS8700_UTPERCOUNT 0.1f
#define DEFAULTB 50.0F // default geomagnetic field (uT)
#define X 0 // vector components
#define Y 1
#define Z 2
#define ONETHIRD 0.33333333F // one third
#define ONESIXTH 0.166666667F // one sixth
#define MINMEASUREMENTS4CAL 40 // minimum number of measurements for 4 element calibration
#define MINMEASUREMENTS7CAL 100 // minimum number of measurements for 7 element calibration
#define MINMEASUREMENTS10CAL 150 // minimum number of measurements for 10 element calibration
#define MINBFITUT 22.0F // minimum geomagnetic field B (uT) for valid calibration
#define MAXBFITUT 67.0F // maximum geomagnetic field B (uT) for valid calibration
#define FITERRORAGINGSECS 7200.0F // 2 hours: time for fit error to increase (age) by e=2.718
static void fUpdateCalibration4INV(MagCalibration_t *MagCal);
static void fUpdateCalibration7EIG(MagCalibration_t *MagCal);
static void fUpdateCalibration10EIG(MagCalibration_t *MagCal);
// run the magnetic calibration
int MagCal_Run(void)
{
int i, j; // loop counters
int isolver; // magnetic solver used
int count=0;
static int waitcount=0;
// only do the calibration occasionally
if (++waitcount < 20) return 0;
waitcount = 0;
// count number of data points
for (i=0; i < MAGBUFFSIZE; i++) {
if (magcal.valid[i]) count++;
}
if (count < MINMEASUREMENTS4CAL) return 0;
if (magcal.ValidMagCal) {
// age the existing fit error to avoid one good calibration locking out future updates
magcal.FitErrorAge *= 1.02f;
}
// is enough data collected
if (count < MINMEASUREMENTS7CAL) {
isolver = 4;
fUpdateCalibration4INV(&magcal); // 4 element matrix inversion calibration
if (magcal.trFitErrorpc < 12.0f) magcal.trFitErrorpc = 12.0f;
} else if (count < MINMEASUREMENTS10CAL) {
isolver = 7;
fUpdateCalibration7EIG(&magcal); // 7 element eigenpair calibration
if (magcal.trFitErrorpc < 7.5f) magcal.trFitErrorpc = 7.5f;
} else {
isolver = 10;
fUpdateCalibration10EIG(&magcal); // 10 element eigenpair calibration
}
// the trial geomagnetic field must be in range (earth is 22uT to 67uT)
if ((magcal.trB >= MINBFITUT) && (magcal.trB <= MAXBFITUT)) {
// always accept the calibration if
// 1: no previous calibration exists
// 2: the calibration fit is reduced or
// 3: an improved solver was used giving a good trial calibration (4% or under)
if ((magcal.ValidMagCal == 0) ||
(magcal.trFitErrorpc <= magcal.FitErrorAge) ||
((isolver > magcal.ValidMagCal) && (magcal.trFitErrorpc <= 4.0F))) {
// accept the new calibration solution
//printf("new magnetic cal, B=%.2f uT\n", magcal.trB);
magcal.ValidMagCal = isolver;
magcal.FitError = magcal.trFitErrorpc;
if (magcal.trFitErrorpc > 2.0f) {
magcal.FitErrorAge = magcal.trFitErrorpc;
} else {
magcal.FitErrorAge = 2.0f;
}
magcal.B = magcal.trB;
magcal.FourBsq = 4.0F * magcal.trB * magcal.trB;
for (i = X; i <= Z; i++) {
magcal.V[i] = magcal.trV[i];
for (j = X; j <= Z; j++) {
magcal.invW[i][j] = magcal.trinvW[i][j];
}
}
return 1; // indicates new calibration applied
}
}
return 0;
}
// 4 element calibration using 4x4 matrix inverse
static void fUpdateCalibration4INV(MagCalibration_t *MagCal)
{
float fBp2; // fBp[X]^2+fBp[Y]^2+fBp[Z]^2
float fSumBp4; // sum of fBp2
float fscaling; // set to FUTPERCOUNT * FMATRIXSCALING
float fE; // error function = r^T.r
int16_t iOffset[3]; // offset to remove large DC hard iron bias in matrix
int16_t iCount; // number of measurements counted
int i, j, k; // loop counters
// working arrays for 4x4 matrix inversion
float *pfRows[4];
int8_t iColInd[4];
int8_t iRowInd[4];
int8_t iPivot[4];
// compute fscaling to reduce multiplications later
fscaling = FXOS8700_UTPERCOUNT / DEFAULTB;
// the trial inverse soft iron matrix invW always equals
// the identity matrix for 4 element calibration
f3x3matrixAeqI(MagCal->trinvW);
// zero fSumBp4=Y^T.Y, vecB=X^T.Y (4x1) and on and above
// diagonal elements of matA=X^T*X (4x4)
fSumBp4 = 0.0F;
for (i = 0; i < 4; i++) {
MagCal->vecB[i] = 0.0F;
for (j = i; j < 4; j++) {
MagCal->matA[i][j] = 0.0F;
}
}
// the offsets are guaranteed to be set from the first element but to avoid compiler error
iOffset[X] = iOffset[Y] = iOffset[Z] = 0;
// use from MINEQUATIONS up to MAXEQUATIONS entries from magnetic buffer to compute matrices
iCount = 0;
for (j = 0; j < MAGBUFFSIZE; j++) {
if (MagCal->valid[j]) {
// use first valid magnetic buffer entry as estimate (in counts) for offset
if (iCount == 0) {
for (k = X; k <= Z; k++) {
iOffset[k] = MagCal->BpFast[k][j];
}
}
// store scaled and offset fBp[XYZ] in vecA[0-2] and fBp[XYZ]^2 in vecA[3-5]
for (k = X; k <= Z; k++) {
MagCal->vecA[k] = (float)((int32_t)MagCal->BpFast[k][j]
- (int32_t)iOffset[k]) * fscaling;
MagCal->vecA[k + 3] = MagCal->vecA[k] * MagCal->vecA[k];
}
// calculate fBp2 = Bp[X]^2 + Bp[Y]^2 + Bp[Z]^2 (scaled uT^2)
fBp2 = MagCal->vecA[3] + MagCal->vecA[4] + MagCal->vecA[5];
// accumulate fBp^4 over all measurements into fSumBp4=Y^T.Y
fSumBp4 += fBp2 * fBp2;
// now we have fBp2, accumulate vecB[0-2] = X^T.Y =sum(Bp2.Bp[XYZ])
for (k = X; k <= Z; k++) {
MagCal->vecB[k] += MagCal->vecA[k] * fBp2;
}
//accumulate vecB[3] = X^T.Y =sum(fBp2)
MagCal->vecB[3] += fBp2;
// accumulate on and above-diagonal terms of matA = X^T.X ignoring matA[3][3]
MagCal->matA[0][0] += MagCal->vecA[X + 3];
MagCal->matA[0][1] += MagCal->vecA[X] * MagCal->vecA[Y];
MagCal->matA[0][2] += MagCal->vecA[X] * MagCal->vecA[Z];
MagCal->matA[0][3] += MagCal->vecA[X];
MagCal->matA[1][1] += MagCal->vecA[Y + 3];
MagCal->matA[1][2] += MagCal->vecA[Y] * MagCal->vecA[Z];
MagCal->matA[1][3] += MagCal->vecA[Y];
MagCal->matA[2][2] += MagCal->vecA[Z + 3];
MagCal->matA[2][3] += MagCal->vecA[Z];
// increment the counter for next iteration
iCount++;
}
}
// set the last element of the measurement matrix to the number of buffer elements used
MagCal->matA[3][3] = (float) iCount;
// store the number of measurements accumulated
MagCal->MagBufferCount = iCount;
// use above diagonal elements of symmetric matA to set both matB and matA to X^T.X
for (i = 0; i < 4; i++) {
for (j = i; j < 4; j++) {
MagCal->matB[i][j] = MagCal->matB[j][i]
= MagCal->matA[j][i] = MagCal->matA[i][j];
}
}
// calculate in situ inverse of matB = inv(X^T.X) (4x4) while matA still holds X^T.X
for (i = 0; i < 4; i++) {
pfRows[i] = MagCal->matB[i];
}
fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 4);
// calculate vecA = solution beta (4x1) = inv(X^T.X).X^T.Y = matB * vecB
for (i = 0; i < 4; i++) {
MagCal->vecA[i] = 0.0F;
for (k = 0; k < 4; k++) {
MagCal->vecA[i] += MagCal->matB[i][k] * MagCal->vecB[k];
}
}
// calculate P = r^T.r = Y^T.Y - 2 * beta^T.(X^T.Y) + beta^T.(X^T.X).beta
// = fSumBp4 - 2 * vecA^T.vecB + vecA^T.matA.vecA
// first set P = Y^T.Y - 2 * beta^T.(X^T.Y) = SumBp4 - 2 * vecA^T.vecB
fE = 0.0F;
for (i = 0; i < 4; i++) {
fE += MagCal->vecA[i] * MagCal->vecB[i];
}
fE = fSumBp4 - 2.0F * fE;
// set vecB = (X^T.X).beta = matA.vecA
for (i = 0; i < 4; i++) {
MagCal->vecB[i] = 0.0F;
for (k = 0; k < 4; k++) {
MagCal->vecB[i] += MagCal->matA[i][k] * MagCal->vecA[k];
}
}
// complete calculation of P by adding beta^T.(X^T.X).beta = vecA^T * vecB
for (i = 0; i < 4; i++) {
fE += MagCal->vecB[i] * MagCal->vecA[i];
}
// compute the hard iron vector (in uT but offset and scaled by FMATRIXSCALING)
for (k = X; k <= Z; k++) {
MagCal->trV[k] = 0.5F * MagCal->vecA[k];
}
// compute the scaled geomagnetic field strength B (in uT but scaled by FMATRIXSCALING)
MagCal->trB = sqrtf(MagCal->vecA[3] + MagCal->trV[X] * MagCal->trV[X] +
MagCal->trV[Y] * MagCal->trV[Y] + MagCal->trV[Z] * MagCal->trV[Z]);
// calculate the trial fit error (percent) normalized to number of measurements
// and scaled geomagnetic field strength
MagCal->trFitErrorpc = sqrtf(fE / (float) MagCal->MagBufferCount) * 100.0F /
(2.0F * MagCal->trB * MagCal->trB);
// correct the hard iron estimate for FMATRIXSCALING and the offsets applied (result in uT)
for (k = X; k <= Z; k++) {
MagCal->trV[k] = MagCal->trV[k] * DEFAULTB
+ (float)iOffset[k] * FXOS8700_UTPERCOUNT;
}
// correct the geomagnetic field strength B to correct scaling (result in uT)
MagCal->trB *= DEFAULTB;
}
// 7 element calibration using direct eigen-decomposition
static void fUpdateCalibration7EIG(MagCalibration_t *MagCal)
{
float det; // matrix determinant
float fscaling; // set to FUTPERCOUNT * FMATRIXSCALING
float ftmp; // scratch variable
int16_t iOffset[3]; // offset to remove large DC hard iron bias
int16_t iCount; // number of measurements counted
int i, j, k, m, n; // loop counters
// compute fscaling to reduce multiplications later
fscaling = FXOS8700_UTPERCOUNT / DEFAULTB;
// the offsets are guaranteed to be set from the first element but to avoid compiler error
iOffset[X] = iOffset[Y] = iOffset[Z] = 0;
// zero the on and above diagonal elements of the 7x7 symmetric measurement matrix matA
for (m = 0; m < 7; m++) {
for (n = m; n < 7; n++) {
MagCal->matA[m][n] = 0.0F;
}
}
// place from MINEQUATIONS to MAXEQUATIONS entries into product matrix matA
iCount = 0;
for (j = 0; j < MAGBUFFSIZE; j++) {
if (MagCal->valid[j]) {
// use first valid magnetic buffer entry as offset estimate (bit counts)
if (iCount == 0) {
for (k = X; k <= Z; k++) {
iOffset[k] = MagCal->BpFast[k][j];
}
}
// apply the offset and scaling and store in vecA
for (k = X; k <= Z; k++) {
MagCal->vecA[k + 3] = (float)((int32_t)MagCal->BpFast[k][j]
- (int32_t)iOffset[k]) * fscaling;
MagCal->vecA[k] = MagCal->vecA[k + 3] * MagCal->vecA[k + 3];
}
// accumulate the on-and above-diagonal terms of
// MagCal->matA=Sigma{vecA^T * vecA}
// with the exception of matA[6][6] which will sum to the number
// of measurements and remembering that vecA[6] equals 1.0F
// update the right hand column [6] of matA except for matA[6][6]
for (m = 0; m < 6; m++) {
MagCal->matA[m][6] += MagCal->vecA[m];
}
// update the on and above diagonal terms except for right hand column 6
for (m = 0; m < 6; m++) {
for (n = m; n < 6; n++) {
MagCal->matA[m][n] += MagCal->vecA[m] * MagCal->vecA[n];
}
}
// increment the measurement counter for the next iteration
iCount++;
}
}
// finally set the last element matA[6][6] to the number of measurements
MagCal->matA[6][6] = (float) iCount;
// store the number of measurements accumulated
MagCal->MagBufferCount = iCount;
// copy the above diagonal elements of matA to below the diagonal
for (m = 1; m < 7; m++) {
for (n = 0; n < m; n++) {
MagCal->matA[m][n] = MagCal->matA[n][m];
}
}
// set tmpA7x1 to the unsorted eigenvalues and matB to the unsorted eigenvectors of matA
eigencompute(MagCal->matA, MagCal->vecA, MagCal->matB, 7);
// find the smallest eigenvalue
j = 0;
for (i = 1; i < 7; i++) {
if (MagCal->vecA[i] < MagCal->vecA[j]) {
j = i;
}
}
// set ellipsoid matrix A to the solution vector with smallest eigenvalue,
// compute its determinant and the hard iron offset (scaled and offset)
f3x3matrixAeqScalar(MagCal->A, 0.0F);
det = 1.0F;
for (k = X; k <= Z; k++) {
MagCal->A[k][k] = MagCal->matB[k][j];
det *= MagCal->A[k][k];
MagCal->trV[k] = -0.5F * MagCal->matB[k + 3][j] / MagCal->A[k][k];
}
// negate A if it has negative determinant
if (det < 0.0F) {
f3x3matrixAeqMinusA(MagCal->A);
MagCal->matB[6][j] = -MagCal->matB[6][j];
det = -det;
}
// set ftmp to the square of the trial geomagnetic field strength B
// (counts times FMATRIXSCALING)
ftmp = -MagCal->matB[6][j];
for (k = X; k <= Z; k++) {
ftmp += MagCal->A[k][k] * MagCal->trV[k] * MagCal->trV[k];
}
// calculate the trial normalized fit error as a percentage
MagCal->trFitErrorpc = 50.0F *
sqrtf(fabs(MagCal->vecA[j]) / (float) MagCal->MagBufferCount) / fabs(ftmp);
// normalize the ellipsoid matrix A to unit determinant
f3x3matrixAeqAxScalar(MagCal->A, powf(det, -(ONETHIRD)));
// convert the geomagnetic field strength B into uT for normalized
// soft iron matrix A and normalize
MagCal->trB = sqrtf(fabs(ftmp)) * DEFAULTB * powf(det, -(ONESIXTH));
// compute trial invW from the square root of A also with normalized
// determinant and hard iron offset in uT
f3x3matrixAeqI(MagCal->trinvW);
for (k = X; k <= Z; k++) {
MagCal->trinvW[k][k] = sqrtf(fabs(MagCal->A[k][k]));
MagCal->trV[k] = MagCal->trV[k] * DEFAULTB + (float)iOffset[k] * FXOS8700_UTPERCOUNT;
}
}
// 10 element calibration using direct eigen-decomposition
static void fUpdateCalibration10EIG(MagCalibration_t *MagCal)
{
float det; // matrix determinant
float fscaling; // set to FUTPERCOUNT * FMATRIXSCALING
float ftmp; // scratch variable
int16_t iOffset[3]; // offset to remove large DC hard iron bias in matrix
int16_t iCount; // number of measurements counted
int i, j, k, m, n; // loop counters
// compute fscaling to reduce multiplications later
fscaling = FXOS8700_UTPERCOUNT / DEFAULTB;
// the offsets are guaranteed to be set from the first element but to avoid compiler error
iOffset[X] = iOffset[Y] = iOffset[Z] = 0;
// zero the on and above diagonal elements of the 10x10 symmetric measurement matrix matA
for (m = 0; m < 10; m++) {
for (n = m; n < 10; n++) {
MagCal->matA[m][n] = 0.0F;
}
}
// sum between MINEQUATIONS to MAXEQUATIONS entries into the 10x10 product matrix matA
iCount = 0;
for (j = 0; j < MAGBUFFSIZE; j++) {
if (MagCal->valid[j]) {
// use first valid magnetic buffer entry as estimate for offset
// to help solution (bit counts)
if (iCount == 0) {
for (k = X; k <= Z; k++) {
iOffset[k] = MagCal->BpFast[k][j];
}
}
// apply the fixed offset and scaling and enter into vecA[6-8]
for (k = X; k <= Z; k++) {
MagCal->vecA[k + 6] = (float)((int32_t)MagCal->BpFast[k][j]
- (int32_t)iOffset[k]) * fscaling;
}
// compute measurement vector elements vecA[0-5] from vecA[6-8]
MagCal->vecA[0] = MagCal->vecA[6] * MagCal->vecA[6];
MagCal->vecA[1] = 2.0F * MagCal->vecA[6] * MagCal->vecA[7];
MagCal->vecA[2] = 2.0F * MagCal->vecA[6] * MagCal->vecA[8];
MagCal->vecA[3] = MagCal->vecA[7] * MagCal->vecA[7];
MagCal->vecA[4] = 2.0F * MagCal->vecA[7] * MagCal->vecA[8];
MagCal->vecA[5] = MagCal->vecA[8] * MagCal->vecA[8];
// accumulate the on-and above-diagonal terms of matA=Sigma{vecA^T * vecA}
// with the exception of matA[9][9] which equals the number of measurements
// update the right hand column [9] of matA[0-8][9] ignoring matA[9][9]
for (m = 0; m < 9; m++) {
MagCal->matA[m][9] += MagCal->vecA[m];
}
// update the on and above diagonal terms of matA ignoring right hand column 9
for (m = 0; m < 9; m++) {
for (n = m; n < 9; n++) {
MagCal->matA[m][n] += MagCal->vecA[m] * MagCal->vecA[n];
}
}
// increment the measurement counter for the next iteration
iCount++;
}
}
// set the last element matA[9][9] to the number of measurements
MagCal->matA[9][9] = (float) iCount;
// store the number of measurements accumulated
MagCal->MagBufferCount = iCount;
// copy the above diagonal elements of symmetric product matrix matA to below the diagonal
for (m = 1; m < 10; m++) {
for (n = 0; n < m; n++) {
MagCal->matA[m][n] = MagCal->matA[n][m];
}
}
// set MagCal->vecA to the unsorted eigenvalues and matB to the unsorted
// normalized eigenvectors of matA
eigencompute(MagCal->matA, MagCal->vecA, MagCal->matB, 10);
// set ellipsoid matrix A from elements of the solution vector column j with
// smallest eigenvalue
j = 0;
for (i = 1; i < 10; i++) {
if (MagCal->vecA[i] < MagCal->vecA[j]) {
j = i;
}
}
MagCal->A[0][0] = MagCal->matB[0][j];
MagCal->A[0][1] = MagCal->A[1][0] = MagCal->matB[1][j];
MagCal->A[0][2] = MagCal->A[2][0] = MagCal->matB[2][j];
MagCal->A[1][1] = MagCal->matB[3][j];
MagCal->A[1][2] = MagCal->A[2][1] = MagCal->matB[4][j];
MagCal->A[2][2] = MagCal->matB[5][j];
// negate entire solution if A has negative determinant
det = f3x3matrixDetA(MagCal->A);
if (det < 0.0F) {
f3x3matrixAeqMinusA(MagCal->A);
MagCal->matB[6][j] = -MagCal->matB[6][j];
MagCal->matB[7][j] = -MagCal->matB[7][j];
MagCal->matB[8][j] = -MagCal->matB[8][j];
MagCal->matB[9][j] = -MagCal->matB[9][j];
det = -det;
}
// compute the inverse of the ellipsoid matrix
f3x3matrixAeqInvSymB(MagCal->invA, MagCal->A);
// compute the trial hard iron vector in offset bit counts times FMATRIXSCALING
for (k = X; k <= Z; k++) {
MagCal->trV[k] = 0.0F;
for (m = X; m <= Z; m++) {
MagCal->trV[k] += MagCal->invA[k][m] * MagCal->matB[m + 6][j];
}
MagCal->trV[k] *= -0.5F;
}
// compute the trial geomagnetic field strength B in bit counts times FMATRIXSCALING
MagCal->trB = sqrtf(fabs(MagCal->A[0][0] * MagCal->trV[X] * MagCal->trV[X] +
2.0F * MagCal->A[0][1] * MagCal->trV[X] * MagCal->trV[Y] +
2.0F * MagCal->A[0][2] * MagCal->trV[X] * MagCal->trV[Z] +
MagCal->A[1][1] * MagCal->trV[Y] * MagCal->trV[Y] +
2.0F * MagCal->A[1][2] * MagCal->trV[Y] * MagCal->trV[Z] +
MagCal->A[2][2] * MagCal->trV[Z] * MagCal->trV[Z] - MagCal->matB[9][j]));
// calculate the trial normalized fit error as a percentage
MagCal->trFitErrorpc = 50.0F * sqrtf(
fabs(MagCal->vecA[j]) / (float) MagCal->MagBufferCount) /
(MagCal->trB * MagCal->trB);
// correct for the measurement matrix offset and scaling and
// get the computed hard iron offset in uT
for (k = X; k <= Z; k++) {
MagCal->trV[k] = MagCal->trV[k] * DEFAULTB + (float)iOffset[k] * FXOS8700_UTPERCOUNT;
}
// convert the trial geomagnetic field strength B into uT for
// un-normalized soft iron matrix A
MagCal->trB *= DEFAULTB;
// normalize the ellipsoid matrix A to unit determinant and
// correct B by root of this multiplicative factor
f3x3matrixAeqAxScalar(MagCal->A, powf(det, -(ONETHIRD)));
MagCal->trB *= powf(det, -(ONESIXTH));
// compute trial invW from the square root of fA (both with normalized determinant)
// set vecA to the unsorted eigenvalues and matB to the unsorted eigenvectors of matA
// where matA holds the 3x3 matrix fA in its top left elements
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
MagCal->matA[i][j] = MagCal->A[i][j];
}
}
eigencompute(MagCal->matA, MagCal->vecA, MagCal->matB, 3);
// set MagCal->matB to be eigenvectors . diag(sqrt(sqrt(eigenvalues))) =
// matB . diag(sqrt(sqrt(vecA))
for (j = 0; j < 3; j++) { // loop over columns j
ftmp = sqrtf(sqrtf(fabs(MagCal->vecA[j])));
for (i = 0; i < 3; i++) { // loop over rows i
MagCal->matB[i][j] *= ftmp;
}
}
// set trinvW to eigenvectors * diag(sqrt(eigenvalues)) * eigenvectors^T =
// matB * matB^T = sqrt(fA) (guaranteed symmetric)
// loop over rows
for (i = 0; i < 3; i++) {
// loop over on and above diagonal columns
for (j = i; j < 3; j++) {
MagCal->trinvW[i][j] = 0.0F;
// accumulate the matrix product
for (k = 0; k < 3; k++) {
MagCal->trinvW[i][j] += MagCal->matB[i][k] * MagCal->matB[j][k];
}
// copy to below diagonal element
MagCal->trinvW[j][i] = MagCal->trinvW[i][j];
}
}
}