Skip to content

Commit

Permalink
fix: move to matrix-inverse 2.0.0 and improve the covariance check
Browse files Browse the repository at this point in the history
  • Loading branch information
piercus committed Jan 22, 2021
1 parent eb76ad7 commit 0bd5c85
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 26 deletions.
5 changes: 3 additions & 2 deletions lib/core-kalman-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,12 @@ class CoreKalmanFilter {
const covarianceInter = matMul(d, previousCorrected.covariance);
const covariancePrevious = matMul(covarianceInter, dTransposed);
const dynCov = this.getValue(this.dynamic.covariance, getValueOptions);

const covariance = add(
dynCov,
covariancePrevious
);
checkMatrix(covariance, [this.dynamic.dimension, this.dynamic.dimension], 'predicted.covariance');

return covariance;
}
Expand All @@ -112,7 +113,7 @@ class CoreKalmanFilter {
if (typeof (index) !== 'number' && typeof (previousCorrected.index) === 'number') {
index = previousCorrected.index + 1;
}

State.check(previousCorrected, {dimension: this.dynamic.dimension});

const getValueOptions = Object.assign({}, {
Expand Down
3 changes: 2 additions & 1 deletion lib/linalgebra/pad-with-zeros.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ module.exports = function (array, {dimension}) {
const l1 = array.length;
const l = array[0].length;
const result = array.map(a => a.concat());

if (dimension < l) {
throw (new TypeError('Dynamic dimension does not match with observedProjection'));
throw (new TypeError(`Dynamic dimension ${dimension} does not match with observedProjection ${l}`));
}

for (let i = 0; i < l1; i++) {
Expand Down
27 changes: 17 additions & 10 deletions lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,31 @@ class State {
/**
* Check the consistency of the State
*/
check() {
this.constructor.check(this);
check(opts) {
this.constructor.check(this, opts);
}

/**
* Check the consistency of the State's attributes
*/

static check(state, {dimension = null} = {}) {
static check(state, {dimension = null, title = null, eigen} = {}) {
if (!(state instanceof State)) {
throw (new TypeError('The argument is not a state'));
throw (new TypeError(
`The argument is not a state \n`+
`Tips: maybe you are using 2 different version of kalman-filter in your npm deps tree`
));
}

const {mean, covariance} = state; // Index
const meanDimension = mean.length;
if (typeof (dimension) === 'number' && meanDimension !== dimension) {
throw (new Error(`State.mean ${mean} with dimension ${meanDimension} does not match expected dimension (${dimension})`));
throw (new Error(`[${title}] State.mean ${mean} with dimension ${meanDimension} does not match expected dimension (${dimension})`));
}

checkMatrix(mean, [meanDimension, 1]);
checkMatrix(covariance, [meanDimension, meanDimension]);
checkCovariance({covariance});
checkMatrix(mean, [meanDimension, 1], title ? title+'-mean' : 'mean');
checkMatrix(covariance, [meanDimension, meanDimension], title ? title+'-covariance' : 'covariance');
checkCovariance({covariance, eigen}, title ? title+'-covariance' : 'covariance');
// If (typeof (index) !== 'number') {
// throw (new TypeError('t must be a number'));
// }
Expand Down Expand Up @@ -75,11 +78,12 @@ class State {
* @returns {State} subState in subspace, with subState.mean.length === obsIndexes.length
*/
subState(obsIndexes) {
return new State({
const state = new State({
mean: obsIndexes.map(i => this.mean[i]),
covariance: subSquareMatrix(this.covariance, obsIndexes),
index: this.index
});
return state;
}

/**
Expand All @@ -90,6 +94,10 @@ class State {
const diff = sub(this.mean, point);
this.check();
const covarianceInvert = invert(this.covariance);
if(covarianceInvert === null){
this.check({eigen: true})
throw(new Error(`Cannot invert covariance ${JSON.stringify(this.covariance)}`))
}
const diffTransposed = transpose(diff);

// Console.log('covariance in obs space', covarianceInObservationSpace);
Expand Down Expand Up @@ -143,7 +151,6 @@ class State {
projectedState = projectedState.subState(obsIndexes);
correctlySizedObservation = obsIndexes.map(i => correctlySizedObservation[i]);
}

return projectedState.rawDetailedMahalanobis(correctlySizedObservation);
}

Expand Down
15 changes: 10 additions & 5 deletions lib/utils/check-covariance.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const tolerance = 0.1;
const Matrix = require('@rayyamhk/matrix');
const checkMatrix = require('./check-matrix');

const checkDefinitePositive = function (covariance, tolerance = 1e-10) {
const covarianceMatrix = new Matrix(covariance);
Expand All @@ -10,25 +11,29 @@ const checkDefinitePositive = function (covariance, tolerance = 1e-10) {
throw new Error(`Eigenvalue should be positive (actual: ${eigenvalue})`);
}
});
console.log('is definite positive', covariance)
};

const checkSymetric = function (covariance) {
const checkSymetric = function (covariance, title = 'checkSymetric') {
covariance.forEach((row, rowId) => row.forEach((item, colId) => {
if (rowId === colId && item < 0) {
throw new Error(`Variance[${colId}] should be positive (actual: ${item})`);
throw new Error(`[${title}] Variance[${colId}] should be positive (actual: ${item})`);
} else if (Math.abs(item) > Math.sqrt(covariance[rowId][rowId] * covariance[colId][colId])) {
console.log(covariance);
throw new Error(`Covariance[${rowId}][${colId}] should verify Cauchy Schwarz Inequality ` +
throw new Error(`[${title}] Covariance[${rowId}][${colId}] should verify Cauchy Schwarz Inequality ` +
`(expected: |x| <= sqrt(${covariance[rowId][rowId]} * ${covariance[colId][colId]})` +
` actual: ${item})`);
} else if (Math.abs(item - covariance[colId][rowId]) > tolerance) {
throw new Error(`Covariance[${rowId}][${colId}] should equal Covariance[${colId}][${rowId}] ` +
` (actual diff: ${Math.abs(item - covariance[colId][rowId])})`);
throw new Error(`[${title}] Covariance[${rowId}][${colId}] should equal Covariance[${colId}][${rowId}] ` +
` (actual diff: ${Math.abs(item - covariance[colId][rowId])}) = ${item} - ${covariance[colId][rowId]}\n` +
`${covariance.join('\n')} is invalid`
);
}
}));
};

module.exports = function ({covariance, eigen = false}) {
checkMatrix(covariance)
checkSymetric(covariance);
if (eigen) {
checkDefinitePositive(covariance);
Expand Down
5 changes: 4 additions & 1 deletion lib/utils/check-matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ const checkShape = require('./check-shape');

module.exports = function (matrix, shape, title = 'checkMatrix') {
if (matrix.reduce((a, b) => a.concat(b)).filter(a => Number.isNaN(a)).length > 0) {
throw (new Error(`[${title}] Matrix should not have a NaN`));
throw (new Error(
`[${title}] Matrix should not have a NaN\nIn : \n` +
matrix.join('\n')
));
}

if (shape) {
Expand Down
3 changes: 3 additions & 0 deletions lib/utils/correlation-to-covariance.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const checkCovariance = require('./check-covariance')

module.exports = function ({correlation, variance}) {
checkCovariance({covariance: correlation})
return correlation.map((c, rowIndex) => c.map((a, colIndex) => a * Math.sqrt(variance[colIndex] * variance[rowIndex])));
};
6 changes: 3 additions & 3 deletions lib/utils/covariance-to-correlation.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const checkCovariance = require('./check-covariance')

module.exports = function (covariance) {
checkCovariance({covariance})
const variance = covariance.map((_, i) => covariance[i][i]);
if (variance.some(v => v <= 0)) {
throw (new Error('variance must be > 0'));
}

return {
variance,
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
},
"dependencies": {
"@rayyamhk/matrix": "^1.0.5",
"matrix-inverse": "^1.0.1"
"matrix-inverse": "^2.0.0"
},
"xo": {
"ignores": [
Expand Down

0 comments on commit 0bd5c85

Please sign in to comment.