Socket
Socket
Sign inDemoInstall

kalman-filter

Package Overview
Dependencies
3
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.11.1 to 1.12.0

93

lib/core-kalman-filter.js

@@ -10,3 +10,3 @@ const matMul = require('../lib/linalgebra/mat-mul.js');

/**
* @callback ObservationCallback
* @callback PreviousCorrectedCallback
* @param {Object} opts

@@ -18,10 +18,3 @@ * @param {Number} opts.index

/**
* @typedef {Object} ObservationConfig
* @property {Number} dimension
* @property {Array.Array.<Number>> | ObservationCallback} stateProjection,
* @property {Array.Array.<Number>> | ObservationCallback} covariance
*/
/**
* @callback DynamicCallback
* @callback PredictedCallback
* @param {Object} opts

@@ -34,8 +27,23 @@ * @param {Number} opts.index

/**
* @typedef {Object} DynamicConfig
* @typedef {Object} ObservationConfig
* @property {Number} dimension
* @property {Array.Array.<Number>> | DynamicCallback} transition,
* @property {Array.Array.<Number>> | DynamicCallback} covariance
* @property {PredictedCallback} [fn=null] for extended kalman filter only, the non-linear state to observation function
* @property {Array.Array.<Number>> | PreviousCorrectedCallback} stateProjection the matrix to transform state to observation (for EKF, the jacobian of the fn)
* @property {Array.Array.<Number>> | PreviousCorrectedCallback} covariance the covariance of the observation noise
*/
/**
* @typedef {Object} DynamicConfig
* @property {Number} dimension dimension of the state vector
* @property {PreviousCorrectedCallback} [fn=null] for extended kalman filter only, the non-linear state-transition model
* @property {Array.Array.<Number>> | PredictedCallback} transition the state-transition model (or for EKF the jacobian of the fn)
* @property {Array.Array.<Number>> | PredictedCallback} covariance the covariance of the process noise
*/
/**
* @typedef {Object} CoreConfig
* @property {DynamicConfig} dynamic the system's dynamic model
* @property {ObservationConfig} observation the system's observation model
* @property {Object} [logger=defaultLogger] a Winston-like logger
*/
const defaultLogger = {

@@ -47,16 +55,8 @@ info: (...args) => console.log(...args),

};
/**
* @class
* @property {DynamicConfig} dynamic the system's dynamic model
* @property {ObservationConfig} observation the system's observation model
*@property logger a Winston-like logger
* @param {CoreConfig} options
*/
class CoreKalmanFilter {
/**
* @param {DynamicConfig} dynamic
* @param {ObservationConfig} observation the system's observation model
*/
constructor({dynamic, observation, logger = defaultLogger}) {
constructor(options) {
const {dynamic, observation, logger = defaultLogger} = options;
this.dynamic = dynamic;

@@ -73,2 +73,3 @@ this.observation = observation;

const {mean: meanInit, covariance: covarianceInit, index: indexInit} = this.dynamic.init;
const initState = new State({

@@ -79,2 +80,3 @@ mean: meanInit,

});
State.check(initState, {title: 'dynamic.init'});
return initState;

@@ -94,6 +96,9 @@ }

const getValueOptions = Object.assign({}, {previousCorrected, index}, options);
const d = this.getValue(this.dynamic.transition, getValueOptions);
const dTransposed = transpose(d);
const covarianceInter = matMul(d, previousCorrected.covariance);
const covariancePrevious = matMul(covarianceInter, dTransposed);
const transition = this.getValue(this.dynamic.transition, getValueOptions);
checkMatrix(transition, [this.dynamic.dimension, this.dynamic.dimension], 'dynamic.transition');
const transitionTransposed = transpose(transition);
const covarianceInter = matMul(transition, previousCorrected.covariance);
const covariancePrevious = matMul(covarianceInter, transitionTransposed);
const dynCov = this.getValue(this.dynamic.covariance, getValueOptions);

@@ -110,2 +115,10 @@

predictMean({opts, transition}) {
if (this.dynamic.fn) {
return this.dynamic.fn(opts);
}
const {previousCorrected} = opts;
return matMul(transition, previousCorrected.mean);
}
/**

@@ -126,12 +139,10 @@ This will return the new prediction, relatively to the dynamic model chosen

State.check(previousCorrected, {dimension: this.dynamic.dimension});
const getValueOptions = Object.assign({}, {
const getValueOptions = Object.assign({}, options, {
previousCorrected,
index
}, options);
const d = this.getValue(this.dynamic.transition, getValueOptions);
});
checkMatrix(d, [this.dynamic.dimension, this.dynamic.dimension], 'dynamic.transition');
const transition = this.getValue(this.dynamic.transition, getValueOptions);
const mean = matMul(d, previousCorrected.mean);
const mean = this.predictMean({transition, opts: getValueOptions});

@@ -158,4 +169,6 @@ const covariance = this.getPredictedCovariance(getValueOptions);

checkMatrix(obsCovariance, [this.observation.dimension, this.observation.dimension], 'observation.covariance');
const stateProjTransposed = transpose(stateProjection);
const stateProjTransposed = transpose(stateProjection);
checkMatrix(stateProjection, [this.observation.dimension, this.dynamic.dimension], 'observation.stateProjection');
const noiselessInnovation = matMul(

@@ -200,2 +213,11 @@ matMul(stateProjection, predicted.covariance),

getPredictedObservation({opts, stateProjection}) {
if (this.observation.fn) {
return this.observation.fn(opts);
}
const {predicted} = opts;
return matMul(stateProjection, predicted.mean);
}
/**

@@ -223,4 +245,5 @@ This will return the new correction, taking into account the prediction made

observation,
matMul(stateProjection, predicted.mean)
this.getPredictedObservation({stateProjection, opts: getValueOptions})
);
const mean = add(

@@ -227,0 +250,0 @@ predicted.mean,

@@ -15,2 +15,14 @@

/**
* @typedef {String} DynamicNonObjectConfig
*/
/**
* @typedef {DynamicConfig} DynamicObjectConfig
* @property {String} name
*/
/**
* @param {DynamicNonObjectConfig} dynamic
* @returns {DynamicObjectConfig}
*/
const buildDefaultDynamic = function (dynamic) {

@@ -24,2 +36,13 @@ if (typeof (dynamic) === 'string') {

/**
* @typedef {String | Number} ObservationNonObjectConfig
*/
/**
* @typedef {ObservationConfig} ObservationObjectConfig
* @property {String} name
*/
/**
* @param {ObservationNonObjectConfig} observation
* @returns {ObservationObjectConfig}
*/
const buildDefaultObservation = function (observation) {

@@ -40,4 +63,5 @@ if (typeof (observation) === 'number') {

*is given, and initialize dynamic.init
*@param {DynamicConfig} options.dynamic
*@param {ObservationConfig} options.observation
*@param {DynamicObjectConfig | DynamicNonObjectConfig} options.dynamic
*@param {ObservationObjectConfig | ObservationNonObjectConfig} options.observation
* @returns {CoreConfig}
*/

@@ -69,7 +93,12 @@

/**
*Returns the corresponding model without arrays as values but only functions
*@param {ObservationConfig} observation
*@param {DynamicConfig} dynamic
*@returns {ObservationConfig, DynamicConfig} model with respect of the Core Kalman Filter properties
* @typedef {Object} ModelsParameters
* @property {DynamicObjectConfig} dynamic
* @property {ObservationObjectConfig} observation
*/
/**
* Returns the corresponding model without arrays as values but only functions
* @param {ModelsParameters} modelToBeChanged
* @returns {CoreConfig} model with respect of the Core Kalman Filter properties
*/
const modelsParametersToCoreOptions = function (modelToBeChanged) {

@@ -79,8 +108,8 @@ const {observation, dynamic} = modelToBeChanged;

observation: {
stateProjection: toFunction(polymorphMatrix(observation.stateProjection)),
covariance: toFunction(polymorphMatrix(observation.covariance, {dimension: observation.dimension}))
stateProjection: toFunction(polymorphMatrix(observation.stateProjection), {label: 'observation.stateProjection'}),
covariance: toFunction(polymorphMatrix(observation.covariance, {dimension: observation.dimension}), {label: 'observation.covariance'})
},
dynamic: {
transition: toFunction(polymorphMatrix(dynamic.transition)),
covariance: toFunction(polymorphMatrix(dynamic.covariance, {dimension: dynamic.dimension}))
transition: toFunction(polymorphMatrix(dynamic.transition), {label: 'dynamic.transition'}),
covariance: toFunction(polymorphMatrix(dynamic.covariance, {dimension: dynamic.dimension}), {label: 'dynamic.covariance'})
}

@@ -92,5 +121,9 @@ });

/**
* @param {DynamicConfig} options.dynamic
* @param {ObservationConfig} options.observation the system's observation model
* @typedef {Object} Config
* @property {DynamicObjectConfig | DynamicNonObjectConfig} dynamic
* @property {ObservationObjectConfig | ObservationNonObjectConfig} observation
*/
/**
* @param {Config} options
*/
constructor(options = {}) {

@@ -126,7 +159,3 @@ const modelsParameters = setupModelsParameters(options);

filterAll(observations) {
const {mean: meanInit, covariance: covarianceInit, index: indexInit} = this.dynamic.init;
let previousCorrected = new State({
mean: meanInit,
covariance: covarianceInit,
index: indexInit});
let previousCorrected = this.getInitState();
const results = [];

@@ -148,4 +177,5 @@ for (const observation of observations) {

* in practice this can be used as a init.covariance value but is very costful calculation (that's why this is not made by default)
* @param {Number} [limitIterations=1e2] max number of iterations
* @param {Number} [tolerance=1e-6] returns when the last values differences are less than tolerance
* @return {<Array.<Array.<Number>>>} covariance
* @return {Array.<Array.<Number>>} covariance
*/

@@ -178,3 +208,3 @@ asymptoticStateCovariance(limitIterations = 1e2, tolerance = 1e-6) {

* @param {Number} [tolerance=1e-6] returns when the last values differences are less than tolerance
* @return {<Array.<Array.<Number>>>} gain
* @return {Array.<Array.<Number>>} gain
*/

@@ -181,0 +211,0 @@ asymptoticGain(tolerance = 1e-6) {

const elemWise = require('./elem-wise');
/**
* Add matrixes together
* @param {...<Array.<Array.<Number>>} args list of matrix
* @param {...Array.<Array.<Number>>} args list of matrix
* @returns {Array.<Array.<Number>>} sum

@@ -6,0 +6,0 @@ */

/**
* Multiply 2 matrixes together
* @param {<Array.<Array.<Number>>} m1
* @param {<Array.<Array.<Number>>} m2
* @param {Array.<Array.<Number>>} m1
* @param {Array.<Array.<Number>>} m2
* @returns {Array.<Array.<Number>>}

@@ -6,0 +6,0 @@ */

@@ -7,3 +7,3 @@ const diag = require('../linalgebra/diag.js');

*@param {DynamicConfig} dynamic
*@returns {ObservationConfig, DynamicConfig}
*@returns {CoreConfig}
*/

@@ -30,3 +30,7 @@

if (dynamic.init && !dynamic.init.mean) {
throw (new Error('dynamic.init should have a mean key'));
}
return {observation, dynamic};
};

@@ -13,3 +13,2 @@ const sub = require('./linalgebra/sub.js');

/**
* @class
* Class representing a multi dimensionnal gaussian, with his mean and his covariance

@@ -29,2 +28,5 @@ * @property {Number} [index=0] the index of the State in the process, this is not mandatory for simple Kalman Filter, but is needed for most of the use case of extended kalman filter

* Check the consistency of the State
* @param {Object} options
* @returns {Null}
* @see check
*/

@@ -37,2 +39,8 @@ check(options) {

* Check the consistency of the State's attributes
* @param {State} state
* @param {Object} [options={}]
* @param {Array} [options.dimension=null] if defined check the dimension of the state
* @param {String} [options.title=null] used to log error mor explicitly
* @param {Boolean} options.eigen
* @returns {Null}
*/

@@ -54,5 +62,5 @@

checkMatrix(mean, [meanDimension, 1], title ? title + '-mean' : 'mean');
checkMatrix(covariance, [meanDimension, meanDimension], title ? title + '-covariance' : 'covariance');
checkCovariance({covariance, eigen}, title ? title + '-covariance' : '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') {

@@ -63,2 +71,8 @@ // throw (new TypeError('t must be a number'));

/**
* Multiply state with matrix
* @param {State} state
* @param {Array.<Array.<Number>>} matrix
* @returns {State}
*/
static matMul({state, matrix}) {

@@ -95,4 +109,11 @@ const covariance = matMul(

/**
* @typedef {Object} DetailedMahalanobis
* @property {Array.<[Number]>} diff
* @property {Array.<Array.<Number>>} covarianceInvert
* @property {Number} value
*/
/**
* Simple Malahanobis distance between the distribution (this) and a point
* @param {Array.<[Number]>} point a Nx1 matrix representing a point
* @returns {DetailedMahalanobis}
*/

@@ -145,2 +166,3 @@ rawDetailedMahalanobis(point) {

* @param {Array.<Number>} obsIndexes list of indexes of observation state to use for the mahalanobis distance
* @returns {DetailedMahalanobis}
*/

@@ -167,5 +189,3 @@ detailedMahalanobis({kf, observation, obsIndexes}) {

/**
* @param {KalmanFilter} kf kalman filter use to project the state in observation's space
* @param {Observation} observation
* @param {Array.<Number>} obsIndexes list of indexes of observation state to use for the mahalanobis distance
* @param {Object} options @see detailedMahalanobis
* @returns {Number}

@@ -172,0 +192,0 @@ */

const checkShape = require('./check-shape');
module.exports = function (matrix, shape, title = 'checkMatrix') {
if (!Array.isArray(matrix)) {
throw (new TypeError(`[${title}] should be a 2-level array matrix and is ${matrix}`));
}
matrix.forEach(row => {
if (!Array.isArray(row)) {
throw (new TypeError(`[${title}] 1-level array should be a matrix ${JSON.stringify(matrix)}`));
}
});
if (matrix.reduce((a, b) => a.concat(b)).filter(a => Number.isNaN(a)).length > 0) {

@@ -5,0 +14,0 @@ throw (new Error(

@@ -6,5 +6,6 @@ const uniq = require('./uniq.js');

/**
*Equivalent to the Object.assign methode, takes several arguments and creates a new object corresponding to the assignment of the arguments
*Equivalent to the Object.assign method, takes several arguments and creates a new object corresponding to the assignment of the arguments
* @param {Object} args
* @param {Number} step
* @returns {Object}
*/

@@ -11,0 +12,0 @@ const deepAssign = function (args, step) {

@@ -14,9 +14,9 @@ /**

*/
module.exports = function (array, {dimension, title = 'polymorph'} = {}) {
if (typeof (array) === 'number' || Array.isArray(array)) {
if (typeof (array) === 'number' && typeof (dimension) === 'number') {
return diag(new Array(dimension).fill(array));
module.exports = function (cov, {dimension, title = 'polymorph'} = {}) {
if (typeof (cov) === 'number' || Array.isArray(cov)) {
if (typeof (cov) === 'number' && typeof (dimension) === 'number') {
return diag(new Array(dimension).fill(cov));
}
if ((Array.isArray(array)) && (Array.isArray(array[0]))) {
if ((Array.isArray(cov)) && (Array.isArray(cov[0]))) {
let shape;

@@ -27,12 +27,12 @@ if (typeof (dimension) === 'number') {

checkMatrix(array, shape, title);
return array;
checkMatrix(cov, shape, title);
return cov;
}
if ((Array.isArray(array)) && (typeof (array[0]) === 'number')) {
return diag(array);
if ((Array.isArray(cov)) && (typeof (cov[0]) === 'number')) {
return diag(cov);
}
}
return array;
return cov;
};

@@ -16,3 +16,3 @@ // Const diag = require('../linalgebra/diag.js');

module.exports = function (array) {
module.exports = function (array, {label = null} = {}) {
if (typeof (array) === 'function') {

@@ -26,3 +26,3 @@ return array;

throw (new Error('Only arrays and functions are authorized'));
throw (new Error(`${label === null ? '' : label + ' : '}Only arrays and functions are authorized (got: "${array}")`));
};
{
"name": "kalman-filter",
"version": "1.11.1",
"version": "1.12.0",
"description": "Kalman filter (and Extended Kalman Filter) Multi-dimensional implementation in Javascript",

@@ -11,3 +11,3 @@ "main": "index.js",

"serve-demo": "http-server docs/",
"build": "webpack"
"build": "NODE_OPTIONS=--openssl-legacy-provider webpack"
},

@@ -14,0 +14,0 @@ "repository": {

@@ -366,3 +366,3 @@ ![Kalman Filter Gif](./demo/demo.gif)

### Extended Kalman Filter
## Play with Kalman Filter

@@ -379,3 +379,2 @@ In order to use the Kalman-Filter with a dynamic or observation model which is not strictly a [General linear model](https://en.wikipedia.org/wiki/General_linear_model), it is possible to use `function` in following parameters :

```js

@@ -451,3 +450,13 @@ const {KalmanFilter} = require('kalman-filter');

```
### Extended
If you want to implement an [extended kalman filter](https://en.wikipedia.org/wiki/Extended_Kalman_filter)
You will need to put your non-linear functions in the following parameters
* `observation.fn`
* `dynamic.fn`
See an example in `test/issues/56.js`
## Use your kalman filter

@@ -463,3 +472,3 @@

```
### Online usage (run it online, forward step only)
### Online filter

@@ -487,3 +496,3 @@ When using online usage (only the forward step), the output of the `filter` method is an instance of the ["State"](/lib/state.js) class.

observations.forEach(observation => {
const predictedState = kFilter.predict({
const predicted = kFilter.predict({
previousCorrected

@@ -621,3 +630,3 @@ });

observations.forEach(observation => {
const predictedState = kFilter.predict({
const predicted = kFilter.predict({
previousCorrected

@@ -653,3 +662,3 @@ });

observations.forEach(observation => {
const predictedState = kFilter.predict({
const predicted = kFilter.predict({
previousCorrected

@@ -656,0 +665,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc