# YAFL Python manual YAFL python extension is provided for prototyping and test purposes, to install it you need to: * go to [Releases](https://github.com/shkolnick-kun/yafl/releases), * download latest yaflpy-\.tar.gz, * install it: ```bash # Cython, numpy, scipy, setuptools, wheel # are needed at this point pip install path_to/yaflpy-\.tar.gz ``` * import the extension: ```Python import yaflpy ``` If you don't want to install the extension then you can do this: ```Python import numpy as np import pyximport import os import scipy.stats import sys # Write the rigth path here! SRC_PATH = 'path_to_yafl/src' EXT_PATH = os.path.join(SRC_PATH, 'yaflpy') sys.path.insert(0, EXT_PATH) pyximport.install( pyimport=True, reload_support=True, language_level=3, setup_args={ 'include_dirs': [np.get_include(), SRC_PATH, EXT_PATH], } ) #Now we are ready to import yaflpy import yaflpy ``` ## YAFL python extension API Actually the API is quite simmilar to [FilterPy](https://filterpy.readthedocs.io/en/latest/index.html). We will focus on YAFL features, goto FilterPy documentation for the rest. ### Constants We have the following constants: ```Python #Bit masks yaflpy.ST_MSK_REGULARIZED = YAFL_ST_MSK_REGULARIZED yaflpy.ST_MSK_GLITCH_SMALL = YAFL_ST_MSK_GLITCH_SMALL yaflpy.ST_MSK_GLITCH_LARGE = YAFL_ST_MSK_GLITCH_LARGE yaflpy.ST_MSK_ANOMALY = YAFL_ST_MSK_ANOMALY yaflpy.ST_MSK_ERROR = 0xFF0 yaflpy.ST_MSK_WARNING = 0xF #Everthing is OK yaflpy.ST_OK = YAFL_ST_OK #Error threshold yaflpy.ST_ERR_THR = YAFL_ST_ERR_THR #Errors: # Invalid argument numer yaflpy.ST_INV_ARG_1 = YAFL_ST_INV_ARG_1 yaflpy.ST_INV_ARG_2 = YAFL_ST_INV_ARG_2 yaflpy.ST_INV_ARG_3 = YAFL_ST_INV_ARG_3 yaflpy.ST_INV_ARG_4 = YAFL_ST_INV_ARG_4 yaflpy.ST_INV_ARG_5 = YAFL_ST_INV_ARG_5 yaflpy.ST_INV_ARG_6 = YAFL_ST_INV_ARG_6 yaflpy.ST_INV_ARG_7 = YAFL_ST_INV_ARG_7 yaflpy.ST_INV_ARG_8 = YAFL_ST_INV_ARG_8 yaflpy.ST_INV_ARG_9 = YAFL_ST_INV_ARG_9 yaflpy.ST_INV_ARG_10 = YAFL_ST_INV_ARG_10 yaflpy.ST_INV_ARG_11 = YAFL_ST_INV_ARG_11 ``` These constants are used to determine statusses of `predict` and `update` methods calls. See [C-Manual](./C-Manual.md) for details. ### Base class The base class is ```Python class yaflpy.yaflKalmanBase(dim_x, dim_z, dt, fx, hx, residual_z = None) ``` where: * `dim_x :int` is state vector dimmension, * `dim_z: int` is measurement vector dimmension, * `dt :float` is timestep between measurements is seconds, * `fx :function(x,dt)` is state transistion function, * `hx :function(x)` is measurement function, * `residual_z :function(x, y)`, optional, is measurement residual function. All other classes extend `yaflKalmanBase`. This base class is not supposed to be used directly. Its API is based on [**UKF**](https://filterpy.readthedocs.io/en/latest/kalman/UnscentedKalmanFilter.html) from FilterPy. **NOTE: Only `predict` and `update` methods are supported by filters in YAFL at the moment.** #### Attributes The Kalman filter has these attrbutes: * `P = Up.dot(Dp.dot(Up.T))` which is state covariance matrix * `Q = Uq.dot(Dq.dot(Uq.T))` which is process noise matrix * `R = Ur.dot(Dr.dot(Ur.T))` which is measurement noise matrix * `rff` which is `R` forgetting factor used to ajust measurement noice covariance at runtime Since YAFL implements UD-factorized filters we don't have these attributes directly, but have vectors to store UD-decomposition elements of these attributes: * `Up :np.array((max(1, (dim_x * (dim_x - 1))//2), ))` is vector with upper triangular elements of P * `Dp :np.array((dim_x, ))` is vector with diagonal elements of P * `Uq :np.array((max(1, (dim_x * (dim_x - 1))//2), ))` is vector with upper triangular elements of Q * `Dq :np.array((dim_x, ))` is vector with diagonal elements of Q * `Ur :np.array((max(1, (dim_z * (dim_z - 1))//2), ))` is vector with upper triangular elements of R * `Dr :np.array((dim_z, ))` is vector with diagonal elements of R ### Notes on Q and R adjustments We used [this paper](https://arxiv.org/pdf/1702.00884.pdf) to implement optional Q and R adaptive adjustments. Here are som notes on our implementation: * All filters in this lib have the optional measurement noice covariance adjustment which can be enabled by setting `rff` attribute to a small positive number e.g. `1e-4`. * All EKF filters in this lib have the optional process noice covariance adjustment which can be enabled by setting `qff` attribute to a small positive number e.g. `1e-4`. * None of UKF filters have the optional process noice covariance adjustment as it leads to filters instability. ### EKF stuf The base class for all **EKF** variants is ```Python class yaflpy.yaflExtendedBase(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` where: * `jfx :function(x,dt)` is function which computes the Jacobian of the `fx` function. Returns: `np.array((dim_x, dim_x))`. * `jhx :function(x)` is function which computes the Jacobian of the `hx` function. Returns: `np.array((dim_x, dim_x))`. This class extends `yaflpy.yaflKalmanBase`. This class is not supposet to be used directly. **NOTE: In addition to `rff` all EKF variants have `qff` attribute which is `Q` forgetting factor used to ajust process noice covariance at runtime** #### Basic EKF variants Basic UD-factorized **EKF** variants are: ```Python class yaflpy.Bierman(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` and ```Python class yaflpy.Joseph(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` Both classes extend `yaflpy.yaflExtendedBase` by implemrnting `_update(self)` method with corresponding update procedures. The example code is: ```Python from yaflpy import Bierman as KF #from yaflpy import Joseph as KF # Measurement std STD = 100. # Bootstrap the filter def _fx(x, dt, **fx_args): x = x.copy() x[0] += x[1] * dt x[2] += x[3] * dt return x def _jfx(x, dt, **fx_args): F = np.array([ [1., dt, 0., 0.], [0., 1., 0., 0.], [0., 0., 1., dt], [0., 0., 0., 1.], ]) return F def _hx(x, **hx_args): if hx_args: print(hx_args) return np.array([x[0], x[2]]) def _jhx(x, **hx_args): H = np.array([ [1., 0., 0., 0.], [0., 0., 1., 0.], ]) return H def _zrf(a,b): return a - b kf = KF(4, 2, 1., _fx, _jfx, _hx, _jhx, residual_z=_zrf) kf.x[0] = 0. kf.x[1] = 0.3 kf.Dp *= .00001 kf.Dq *= 1.0e-8 kf.Dr *= STD*STD kf.Dr[0] *= .75 kf.Ur += 0.5 # Generate some data N = 6000 clean = np.zeros((N, 2)) noisy = np.zeros((N, 2)) kf_out = np.zeros((N, 2)) t = np.zeros((N,), dtype=np.float) for i in range(1, len(clean)): clean[i] = clean[i-1] + np.array([1.,1.]) noisy[i] = clean[i] + np.random.normal(scale=STD, size=2) t[i] = i #Run the filter for i, z in enumerate(noisy): kf.predict() kf.update(z) kf_out[i] = kf.x[::2] ``` #### Adaptive EKF variants Basic class for adaptive UD-factorized **EKF** variants is ```Python class yaflpy.yaflAdaptiveBase(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` This class is based on `yaflpy.yaflExtendedBase` and adds `chi2` attribute to it. This class is not supposed to be used directly. Adaptive UD-factorized **EKF** variants are: ```Python class yaflpy.AdaptiveBierman(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` and ```Python class yaflpy.AdaptiveJoseph(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` Both classes extend `yaflpy.yaflAdaptiveBase` by implementing `_update(self)` method with corresponding update procedures. Both classes APIs are simmilar to basic variants. #### Robust EKF variants Basic class for adaptive UD-factorized **EKF** variants is ```Python class yaflpy.yaflRobustBase(dim_x, dim_z, dt, fx, jfx, hx, jhx, gz=None, gdotz=None, **kwargs) ``` This class is based on `yaflpy.yaflExtendedBase` and adds the following hidden attrbutes to it: * `gz :function(nu, **hx_args)` is influence function. Returns: `float`. * `gdotz :function(nu, **hx_args)` is influence function first derivative. Returns: `float`. Both functions take `hx_args` which are `hx` function **kwargs** This class is not supposed to be used directly. Adaptive UD-factorized **EKF** variants are: ```Python class yaflpy.RobustBierman(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` and ```Python class yaflpy.RobustJoseph(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` Both classes extend `yaflpy.yaflRobustBase` by implementing `_update(self)` method with corresponding update procedures. Both classes APIs are simmilar to basic variants. #### Adaptive robust EKF variants Basic class for adaptive UD-factorized **EKF** variants is ```Python class yaflpy.yaflAdaptiveRobustBase(dim_x, dim_z, dt, fx, jfx, hx, jhx, gz=None, gdotz=None, **kwargs) ``` This class is based on `yaflpy.yaflRobustBase` and adds `chi2` attribute to it. This class is not supposed to be used directly. Adaptive UD-factorized **EKF** variants are: ```Python class yaflpy.AdaptiveRobustBierman(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` and ```Python class yaflpy.AdaptiveRobustJoseph(dim_x, dim_z, dt, fx, jfx, hx, jhx, residual_z = None) ``` Both classes extend `yaflpy.yaflAdaptiveRobustBase` by implementing `_update(self)` method with corresponding update procedures. Both classes APIs are simmilar to basic variants. ### UKF stuf #### Sigma points generator base class The base class for sigma points generators is: ```Python class yaflpy.yaflSigmaBase(dim_x, addf=None) ``` where: * `addf: function(mean, delta, mult)` which computes `sigma_point = mean + delta * mult` This class is a wrapper around `yaflUKFBaseSt` type from [src/yafl.h](../src/yafl.h). This class is not supposed to be used directly. #### Van der Merwe sigma points generator The generator class is: ```Python class MerweSigmaPoints(dim_x, alpha, beta, kappa=0.0, **kwargs) ``` This class is a wrapper around `yaflUKFMerweSt`. It extends `yaflSigmaBase` and adds the following attributes: * `alpha` is a scaling parameter that determines the width of the sigma point spread. * `beta` is used to account for prior knowledge about the distribution of the mean. * `kappa` is a secondary scaling parameter usually set to 0 or 3-dim_x. #### Julier sigma points generator The generator class is: ```Python class JulierSigmaPoints(dim_x, kappa=0.0, **kwargs) ``` This class is a wrapper around `yaflUKFJulierSt`. It extends `yaflSigmaBase` and adds the following attributes: * `kappa` is a secondary scaling parameter usually set to `0` or `3-dim_x`. #### Filter base class The base class for all **UKF** variants is ```Python class yaflpy.yaflUnscentedBase(dim_x, dim_z, dt, hx, fx, points, x_mean_fn=None, z_mean_fn=None, residual_x=None, residual_z=None) ``` where: * `points :yaflSigmaBase` is a sigma points generator object. * `x_mean_fn :function(state_sigmas)` is function which computes state sigma points mean. Takes `np.array((points_number, dim_x))`, returns `np.array((dim_x,))`. * `z_mean_fn :function(measurement_sigmas)` is function which computes measurements sigma points mean. Takes `np.array((points_number, dim_z))`, returns `np.array((dim_z,))`. * `residual_x :function(a, b)`, optional, is state residual function. This class is very simmilar to `filterpy.kalman.UnscentedKalmanFilter`, see FilterPy [docs](https://filterpy.readthedocs.io/en/latest/kalman/UnscentedKalmanFilter.html). This class extends `yaflpy.yaflKalmanBase`. This class is not supposet to be used directly. #### Bierman UKF variants ##### Basic Bierman **UKF** class ```Python class yaflpy.UnscentedBierman(dim_x, dim_z, dt, hx, fx, points, x_mean_fn=None, z_mean_fn=None, residual_x=None, residual_z=None) ``` This class extends `yaflpy.yaflUnscentedBase` by implementing `_update(self)` method. The example code is: ```Python from yaflpy import MerweSigmaPoints as SP from yaflpy import UnscentedBierman as KF # Bootstrap the filter STD = 100. def _fx(x, dt, **fx_args): x = x.copy() x[0] += x[1] * dt x[2] += x[3] * dt return x def _hx(x, **hx_args): if hx_args: print(hx_args) return np.array([x[0], x[2]]) def _zrf(a,b): return a - b sp = SP(4, 0.1, 2., 0) kf = KF(4, 2, 1., _hx, _fx, sp, residual_z=_zrf) kf.x[0] = 0. kf.x[1] = 0.3 kf.Dp *= .00001 kf.Dq *= 1.0e-8 kf.Dr *= STD*STD kf.Dr[0] *= .75 kf.Ur += .5 # Generate some data N = 6000 clean = np.zeros((N, 2)) noisy = np.zeros((N, 2)) kf_out = np.zeros((N, 2)) t = np.zeros((N,), dtype=np.float) for i in range(1, len(clean)): clean[i] = clean[i-1] + np.array([1.,1.]) noisy[i] = clean[i] + np.random.normal(scale=STD, size=2) t[i] = i # Run the filter for i, z in enumerate(noisy): kf.predict() kf.update(z) kf_out[i] = kf.zp ``` ##### Adaptive Bierman **UKF** class ```Python class yaflpy.UnscentedAdaptiveBierman(dim_x, dim_z, yaflFloat dt, hx, fx, points, **kwargs) ``` This class extends `yaflpy.yaflUnscentedBase` by addition of `chi2` attribute and implementation of `_update(self)` method. THe usage is similar to `yaflpy.UnscentedBierman`. ##### Robust Bierman **UKF** class ```Python class yaflpy.UnscentedRobustBierman(dim_x, dim_z, dt, hx, fx, points, gz=None, gdotz=None, **kwargs) ``` This class extends `yaflpy.yaflRobustUKFBase` by implementation of `_update(self)` method. THe usage is similar to `yaflpy.UnscentedBierman`. The `yaflpy.yaflRobustUKFBase` class extends `yaflpy.yaflUnscentedBase` by addition of the following hidden attrbutes: * `gz :function(nu, **hx_args)` is influence function. Returns: `float`. * `gdotz :function(nu, **hx_args)` is influence function first derivative. Returns: `float`. ##### Adaptive robust Bierman **UKF** class ```Python class yaflpy.UnscentedAdaptiveRobustBierman(dim_x, dim_z, dt, hx, fx, points, **kwargs) ``` This class extends `yaflpy.yaflAdaptiveRobustUKFBase` by implementation of `_update(self)` method. THe usage is similar to `yaflpy.UnscentedBierman`. The `yaflpy.yaflAdaptiveRobustUKFBase` class extends `yaflpy.yaflRobustUKFBase` by addition `chi2` attribute. #### UD-factorized "Full" UKF variants ##### Unscented ```Python class yaflpy.Unscented(dim_x, dim_z, dt, hx, fx, points, **kwargs) ``` This class extends `yaflpy.yaflUnscentedBase` by implementation of `_update` method and by addition of the following attributes: * `Us :np.array((max(1, (dim_x * (dim_x - 1))//2), ))` is vector with upper triangular elements of S * `Ds :np.array((dim_x, ))` is vector with diagonal elements of S See FilterPy [docs](https://filterpy.readthedocs.io/en/latest/kalman/UnscentedKalmanFilter.html) for details. THe usage is similar to `yaflpy.UnscentedBierman`. ##### UnscendedAdaptive ```Python class yaflpy.UnscentedAdaptive(dim_x, dim_z, dt, hx, fx, points, aplha = 0.000001, **kwargs) ``` where: * `alpha: float` is probability margin which is use to initiate `chi2` attribute by `scipy.stats.chi2.ppf(1.0 - aplha, dim_z)` This class extends `yaflpy.Unscented` by implementation of `_update` method and by addition of `chi2` attribute. THe usage is similar to `yaflpy.UnscentedBierman`. ## Good luck \%usename\% Happy prototyping with YAFL python extension.