2015-04-07 18:20:11 +03:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2016-03-08 17:56:20 +02:00
|
|
|
|
# Copyright (c) 2015, Alex Grigorevskiy
|
|
|
|
|
|
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-08 17:56:20 +02:00
|
|
|
|
Main functionality for state-space inference.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
import collections # for cheking whether a variable is iterable
|
|
|
|
|
|
import types # for cheking whether a variable is a function
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
import scipy as sp
|
|
|
|
|
|
import scipy.linalg as linalg
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
try:
|
2016-06-09 14:50:53 +03:00
|
|
|
|
from . import state_space_setup
|
2015-10-08 15:30:34 +03:00
|
|
|
|
setup_available = True
|
|
|
|
|
|
except ImportError as e:
|
|
|
|
|
|
setup_available = False
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
print_verbose = False
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
try:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
import state_space_cython
|
2015-09-04 19:44:19 +03:00
|
|
|
|
cython_code_available = True
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if print_verbose:
|
|
|
|
|
|
print("state_space: cython is available")
|
2015-09-04 19:44:19 +03:00
|
|
|
|
except ImportError as e:
|
|
|
|
|
|
cython_code_available = False
|
|
|
|
|
|
|
|
|
|
|
|
#cython_code_available = False
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# Use cython by default
|
|
|
|
|
|
use_cython = False
|
|
|
|
|
|
if setup_available:
|
|
|
|
|
|
use_cython = state_space_setup.use_cython
|
|
|
|
|
|
|
|
|
|
|
|
if print_verbose:
|
|
|
|
|
|
if use_cython:
|
|
|
|
|
|
print("state_space: cython is used")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("state_space: cython is NOT used")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class Dynamic_Callables_Python(object):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def f_a(self, k, m, A):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
p_a: function (k, x_{k-1}, A_{k}). Dynamic function.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
x_{k-1} State from the previous step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
A_{k} Jacobian matrices of f_a. In the linear case it is exactly
|
|
|
|
|
|
A_{k}.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
raise NotImplemented("f_a is not implemented!")
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def Ak(self, k, m, P): # returns state iteration matrix
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function (k, m, P) return Jacobian of dynamic function, it is passed
|
|
|
|
|
|
into p_a.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
raise NotImplemented("Ak is not implemented!")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Qk(self, k):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k). Returns noise matrix of dynamic model on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplemented("Qk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def Q_srk(self, k):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function (k). Returns the square root of noise matrix of dynamic model
|
|
|
|
|
|
on iteration k.
|
|
|
|
|
|
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
This function is implemented to use SVD prediction step.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
raise NotImplemented("Q_srk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def dAk(self, k):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k). Returns the derivative of A on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplemented("dAk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def dQk(self, k):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k). Returns the derivative of Q on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise NotImplemented("dQk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self, compute_derivatives=False):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Return the state of this object to the beginning of iteration
|
|
|
|
|
|
(to k eq. 0).
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
raise NotImplemented("reset is not implemented!")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Dynamic_Callables_Class = state_space_cython.Dynamic_Callables_Cython
|
|
|
|
|
|
else:
|
|
|
|
|
|
Dynamic_Callables_Class = Dynamic_Callables_Python
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
|
|
|
|
|
|
class Measurement_Callables_Python(object):
|
|
|
|
|
|
def f_h(self, k, m_pred, Hk):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k, x_{k}, H_{k}). Measurement function.
|
|
|
|
|
|
k (iteration number), starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
x_{k} state
|
|
|
|
|
|
H_{k} Jacobian matrices of f_h. In the linear case it is exactly
|
|
|
|
|
|
H_{k}.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
raise NotImplemented("f_a is not implemented!")
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def Hk(self, k, m_pred, P_pred): # returns state iteration matrix
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
function (k, m, P) return Jacobian of measurement function, it is
|
|
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
raise NotImplemented("Hk is not implemented!")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Rk(self, k):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function (k). Returns noise matrix of measurement equation
|
2015-09-04 19:44:19 +03:00
|
|
|
|
on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplemented("Rk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def R_isrk(self, k):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function (k). Returns the square root of the noise matrix of
|
|
|
|
|
|
measurement equation on iteration k.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
k (iteration number). starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
This function is implemented to use SVD prediction step.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
raise NotImplemented("Q_srk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def dHk(self, k):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k). Returns the derivative of H on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplemented("dAk is not implemented!")
|
|
|
|
|
|
|
|
|
|
|
|
def dRk(self, k):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k). Returns the derivative of R on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
|
|
|
|
|
raise NotImplemented("dQk is not implemented!")
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def reset(self, compute_derivatives=False):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Return the state of this object to the beginning of iteration
|
|
|
|
|
|
(to k eq. 0)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
raise NotImplemented("reset is not implemented!")
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Measurement_Callables_Class = state_space_cython.\
|
|
|
|
|
|
Measurement_Callables_Cython
|
2015-09-04 19:44:19 +03:00
|
|
|
|
else:
|
|
|
|
|
|
Measurement_Callables_Class = Measurement_Callables_Python
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class R_handling_Python(Measurement_Callables_Class):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The calss handles noise matrix R.
|
|
|
|
|
|
"""
|
|
|
|
|
|
def __init__(self, R, index, R_time_var_index, unique_R_number, dR=None):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Input:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
---------------
|
|
|
|
|
|
R - array with noise on various steps. The result of preprocessing
|
|
|
|
|
|
the noise input.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
index - for each step of Kalman filter contains the corresponding index
|
|
|
|
|
|
in the array.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
R_time_var_index - another index in the array R. Computed earlier and
|
|
|
|
|
|
is passed here.
|
|
|
|
|
|
|
|
|
|
|
|
unique_R_number - number of unique noise matrices below which square
|
|
|
|
|
|
roots are cached and above which they are computed each time.
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dR: 3D array[:, :, param_num]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
derivative of R. Derivative is supported only when R do not change
|
|
|
|
|
|
over time
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
Object which has two necessary functions:
|
|
|
|
|
|
f_R(k)
|
|
|
|
|
|
inv_R_square_root(k)
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.R = R
|
|
|
|
|
|
self.index = index
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.R_time_var_index = int(R_time_var_index)
|
|
|
|
|
|
self.dR = dR
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if (len(np.unique(index)) > unique_R_number):
|
|
|
|
|
|
self.svd_each_time = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.svd_each_time = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.R_square_root = {}
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
def Rk(self, k):
|
|
|
|
|
|
return self.R[:, :, self.index[self.R_time_var_index, k]]
|
|
|
|
|
|
|
|
|
|
|
|
def dRk(self, k):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if self.dR is None:
|
|
|
|
|
|
raise ValueError("dR derivative is None")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return self.dR # the same dirivative on each iteration
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def R_isrk(self, k):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Function returns the inverse square root of R matrix on step k.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
ind = int(self.index[self.R_time_var_index, k])
|
|
|
|
|
|
R = self.R[:, :, ind]
|
|
|
|
|
|
|
|
|
|
|
|
if (R.shape[0] == 1): # 1-D case handle simplier. No storage
|
|
|
|
|
|
# of the result, just compute it each time.
|
|
|
|
|
|
inv_square_root = np.sqrt(1.0/R)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
else:
|
|
|
|
|
|
if self.svd_each_time:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
(U, S, Vh) = sp.linalg.svd(R, full_matrices=False,
|
|
|
|
|
|
compute_uv=True, overwrite_a=False,
|
|
|
|
|
|
check_finite=True)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
inv_square_root = U * 1.0/np.sqrt(S)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if ind in self.R_square_root:
|
|
|
|
|
|
inv_square_root = self.R_square_root[ind]
|
|
|
|
|
|
else:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
(U, S, Vh) = sp.linalg.svd(R, full_matrices=False,
|
|
|
|
|
|
compute_uv=True,
|
|
|
|
|
|
overwrite_a=False,
|
|
|
|
|
|
check_finite=True)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
inv_square_root = U * 1.0/np.sqrt(S)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.R_square_root[ind] = inv_square_root
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return inv_square_root
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
R_handling_Class = state_space_cython.R_handling_Cython
|
|
|
|
|
|
else:
|
|
|
|
|
|
R_handling_Class = R_handling_Python
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class Std_Measurement_Callables_Python(R_handling_Class):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, H, H_time_var_index, R, index, R_time_var_index,
|
|
|
|
|
|
unique_R_number, dH=None, dR=None):
|
|
|
|
|
|
super(Std_Measurement_Callables_Python,
|
|
|
|
|
|
self).__init__(R, index, R_time_var_index, unique_R_number, dR)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.H = H
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.H_time_var_index = int(H_time_var_index)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.dH = dH
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def f_h(self, k, m, H):
|
|
|
|
|
|
"""
|
|
|
|
|
|
function (k, x_{k}, H_{k}). Measurement function.
|
|
|
|
|
|
k (iteration number), starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
x_{k} state
|
|
|
|
|
|
H_{k} Jacobian matrices of f_h. In the linear case it is exactly
|
|
|
|
|
|
H_{k}.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
return np.dot(H, m)
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def Hk(self, k, m_pred, P_pred): # returns state iteration matrix
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
function (k, m, P) return Jacobian of measurement function, it is
|
|
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
return self.H[:, :, self.index[self.H_time_var_index, k]]
|
|
|
|
|
|
|
|
|
|
|
|
def dHk(self, k):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if self.dH is None:
|
|
|
|
|
|
raise ValueError("dH derivative is None")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return self.dH # the same dirivative on each iteration
|
2015-09-04 19:44:19 +03:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Std_Measurement_Callables_Class = state_space_cython.\
|
|
|
|
|
|
Std_Measurement_Callables_Cython
|
2015-09-04 19:44:19 +03:00
|
|
|
|
else:
|
|
|
|
|
|
Std_Measurement_Callables_Class = Std_Measurement_Callables_Python
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class Q_handling_Python(Dynamic_Callables_Class):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, Q, index, Q_time_var_index, unique_Q_number, dQ=None):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Input:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
---------------
|
|
|
|
|
|
R - array with noise on various steps. The result of preprocessing
|
|
|
|
|
|
the noise input.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
index - for each step of Kalman filter contains the corresponding index
|
|
|
|
|
|
in the array.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
R_time_var_index - another index in the array R. Computed earlier and
|
|
|
|
|
|
passed here.
|
|
|
|
|
|
|
|
|
|
|
|
unique_R_number - number of unique noise matrices below which square
|
|
|
|
|
|
roots are cached and above which they are computed each time.
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dQ: 3D array[:, :, param_num]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
derivative of Q. Derivative is supported only when Q do not
|
|
|
|
|
|
change over time
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
Object which has two necessary functions:
|
|
|
|
|
|
f_R(k)
|
|
|
|
|
|
inv_R_square_root(k)
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.Q = Q
|
|
|
|
|
|
self.index = index
|
|
|
|
|
|
self.Q_time_var_index = Q_time_var_index
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.dQ = dQ
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if (len(np.unique(index)) > unique_Q_number):
|
|
|
|
|
|
self.svd_each_time = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.svd_each_time = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.Q_square_root = {}
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
def Qk(self, k):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
function (k). Returns noise matrix of dynamic model on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
return self.Q[:, :, self.index[self.Q_time_var_index, k]]
|
|
|
|
|
|
|
|
|
|
|
|
def dQk(self, k):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if self.dQ is None:
|
|
|
|
|
|
raise ValueError("dQ derivative is None")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return self.dQ # the same dirivative on each iteration
|
|
|
|
|
|
|
|
|
|
|
|
def Q_srk(self, k):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function (k). Returns the square root of noise matrix of dynamic model
|
|
|
|
|
|
on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
This function is implemented to use SVD prediction step.
|
|
|
|
|
|
"""
|
|
|
|
|
|
ind = self.index[self.Q_time_var_index, k]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Q = self.Q[:, :, ind]
|
|
|
|
|
|
|
|
|
|
|
|
if (Q.shape[0] == 1): # 1-D case handle simplier. No storage
|
|
|
|
|
|
# of the result, just compute it each time.
|
|
|
|
|
|
square_root = np.sqrt(Q)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
else:
|
|
|
|
|
|
if self.svd_each_time:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
(U, S, Vh) = sp.linalg.svd(Q, full_matrices=False,
|
|
|
|
|
|
compute_uv=True,
|
|
|
|
|
|
overwrite_a=False,
|
|
|
|
|
|
check_finite=True)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
square_root = U * np.sqrt(S)
|
|
|
|
|
|
else:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if ind in self.Q_square_root:
|
|
|
|
|
|
square_root = self.Q_square_root[ind]
|
|
|
|
|
|
else:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
(U, S, Vh) = sp.linalg.svd(Q, full_matrices=False,
|
|
|
|
|
|
compute_uv=True,
|
|
|
|
|
|
overwrite_a=False,
|
|
|
|
|
|
check_finite=True)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
square_root = U * np.sqrt(S)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.Q_square_root[ind] = square_root
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return square_root
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Q_handling_Class = state_space_cython.Q_handling_Cython
|
|
|
|
|
|
else:
|
|
|
|
|
|
Q_handling_Class = Q_handling_Python
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class Std_Dynamic_Callables_Python(Q_handling_Class):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, A, A_time_var_index, Q, index, Q_time_var_index,
|
|
|
|
|
|
unique_Q_number, dA=None, dQ=None):
|
|
|
|
|
|
super(Std_Dynamic_Callables_Python,
|
|
|
|
|
|
self).__init__(Q, index, Q_time_var_index, unique_Q_number, dQ)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.A = A
|
|
|
|
|
|
self.A_time_var_index = A_time_var_index
|
|
|
|
|
|
self.dA = dA
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def f_a(self, k, m, A):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
f_a: function (k, x_{k-1}, A_{k}). Dynamic function.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
x_{k-1} State from the previous step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
A_{k} Jacobian matrices of f_a. In the linear case it is exactly
|
|
|
|
|
|
A_{k}.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
return np.dot(A, m)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def Ak(self, k, m_pred, P_pred): # returns state iteration matrix
|
2015-09-04 19:44:19 +03:00
|
|
|
|
"""
|
|
|
|
|
|
function (k, m, P) return Jacobian of measurement function, it is
|
|
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
return self.A[:, :, self.index[self.A_time_var_index, k]]
|
|
|
|
|
|
|
|
|
|
|
|
def dAk(self, k):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if self.dA is None:
|
|
|
|
|
|
raise ValueError("dA derivative is None")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return self.dA # the same dirivative on each iteration
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self, compute_derivatives=False):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Return the state of this object to the beginning of iteration
|
|
|
|
|
|
(to k eq. 0)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
return self
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Std_Dynamic_Callables_Class = state_space_cython.\
|
|
|
|
|
|
Std_Dynamic_Callables_Cython
|
2015-09-04 19:44:19 +03:00
|
|
|
|
else:
|
|
|
|
|
|
Std_Dynamic_Callables_Class = Std_Dynamic_Callables_Python
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddMethodToClass(object):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def __init__(self, func=None, tp='staticmethod'):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Input:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
func: function to add
|
|
|
|
|
|
tp: string
|
|
|
|
|
|
Type of the method: normal, staticmethod, classmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
"""
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if func is None:
|
|
|
|
|
|
raise ValueError("Function can not be None")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.func = func
|
|
|
|
|
|
self.tp = tp
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
def __get__(self, obj, klass=None, *args, **kwargs):
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if self.tp == 'staticmethod':
|
|
|
|
|
|
return self.func
|
|
|
|
|
|
elif self.tp == 'normal':
|
|
|
|
|
|
def newfunc(obj, *args, **kwargs):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
return self.func
|
|
|
|
|
|
|
|
|
|
|
|
elif self.tp == 'classmethod':
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def newfunc(klass, *args, **kwargs):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
return self.func
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return newfunc
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class DescreteStateSpaceMeta(type):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Substitute necessary methods from cython.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def __new__(typeclass, name, bases, attributes):
|
|
|
|
|
|
"""
|
|
|
|
|
|
After thos method the class object is created
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if '_kalman_prediction_step_SVD' in attributes:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
attributes['_kalman_prediction_step_SVD'] =\
|
|
|
|
|
|
AddMethodToClass(state_space_cython.
|
|
|
|
|
|
_kalman_prediction_step_SVD_Cython)
|
|
|
|
|
|
|
|
|
|
|
|
if '_kalman_update_step_SVD' in attributes:
|
|
|
|
|
|
attributes['_kalman_update_step_SVD'] =\
|
|
|
|
|
|
AddMethodToClass(state_space_cython.
|
|
|
|
|
|
_kalman_update_step_SVD_Cython)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if '_cont_discr_kalman_filter_raw' in attributes:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
attributes['_cont_discr_kalman_filter_raw'] =\
|
|
|
|
|
|
AddMethodToClass(state_space_cython.
|
|
|
|
|
|
_cont_discr_kalman_filter_raw_Cython)
|
|
|
|
|
|
|
|
|
|
|
|
return super(DescreteStateSpaceMeta,
|
|
|
|
|
|
typeclass).__new__(typeclass, name, bases, attributes)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
class DescreteStateSpace(object):
|
|
|
|
|
|
"""
|
|
|
|
|
|
This class implents state-space inference for linear and non-linear
|
|
|
|
|
|
state-space models.
|
|
|
|
|
|
Linear models are:
|
|
|
|
|
|
x_{k} = A_{k} * x_{k-1} + q_{k-1}; q_{k-1} ~ N(0, Q_{k-1})
|
|
|
|
|
|
y_{k} = H_{k} * x_{k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Nonlinear:
|
|
|
|
|
|
x_{k} = f_a(k, x_{k-1}, A_{k}) + q_{k-1}; q_{k-1} ~ N(0, Q_{k-1})
|
|
|
|
|
|
y_{k} = f_h(k, x_{k}, H_{k}) + r_{k}; r_{k-1} ~ N(0, R_{k})
|
|
|
|
|
|
Here f_a and f_h are some functions of k (iteration number), x_{k-1} or
|
|
|
|
|
|
x_{k} (state value on certain iteration), A_{k} and H_{k} - Jacobian
|
|
|
|
|
|
matrices of f_a and f_h respectively. In the linear case they are exactly
|
|
|
|
|
|
A_{k} and H_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Currently two nonlinear Gaussian filter algorithms are implemented:
|
|
|
|
|
|
Extended Kalman Filter (EKF), Statistically linearized Filter (SLF), which
|
|
|
|
|
|
implementations are very similar.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-09-04 19:44:19 +03:00
|
|
|
|
__metaclass__ = DescreteStateSpaceMeta
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@staticmethod
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def _reshape_input_data(shape, desired_dim=3):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Static function returns the column-wise shape for for an input shape.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
--------------
|
|
|
|
|
|
shape: tuple
|
|
|
|
|
|
Shape of an input array, so that it is always a column.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
desired_dim: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
desired shape of output. For Y data it should be 3
|
2015-09-04 19:44:19 +03:00
|
|
|
|
(sample_no, dimension, ts_no). For X data - 2 (sample_no, 1)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
new_shape: tuple
|
2016-03-15 17:01:05 +02:00
|
|
|
|
New shape of the measurements array. Idea is that samples are
|
2015-09-04 19:44:19 +03:00
|
|
|
|
along dimension 0, sample dimension - dimension 1, different
|
|
|
|
|
|
time series - dimension 2.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
old_shape: tuple or None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
If the shape has been modified, return old shape, otherwise
|
|
|
|
|
|
None.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if (len(shape) > 3):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError("""Input array is not supposed to be more
|
|
|
|
|
|
than 3 dimensional.""")
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if (len(shape) > desired_dim):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError("Input array shape is more than desired shape.")
|
2015-04-07 18:20:11 +03:00
|
|
|
|
elif len(shape) == 1:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
if (desired_dim == 3):
|
|
|
|
|
|
return ((shape[0], 1, 1), shape) # last dimension is the
|
|
|
|
|
|
# time serime_series_no
|
|
|
|
|
|
elif (desired_dim == 2):
|
|
|
|
|
|
return ((shape[0], 1), shape)
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
elif len(shape) == 2:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
if (desired_dim == 3):
|
|
|
|
|
|
return ((shape[1], 1, 1), shape) if (shape[0] == 1) else\
|
|
|
|
|
|
((shape[0], shape[1], 1), shape) # convert to column
|
|
|
|
|
|
# vector
|
|
|
|
|
|
elif (desired_dim == 2):
|
|
|
|
|
|
return ((shape[1], 1), shape) if (shape[0] == 1) else\
|
|
|
|
|
|
((shape[0], shape[1]), None) # convert to column vector
|
|
|
|
|
|
|
|
|
|
|
|
else: # len(shape) == 3
|
|
|
|
|
|
return (shape, None) # do nothing
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@classmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def kalman_filter(cls, p_A, p_Q, p_H, p_R, Y, index=None, m_init=None,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
P_init=None, p_kalman_filter_type='regular',
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_log_likelihood=False,
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_grad_log_likelihood=False, grad_params_no=None,
|
|
|
|
|
|
grad_calc_params=None):
|
|
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
This function implements the basic Kalman Filter algorithm
|
|
|
|
|
|
These notations for the State-Space model are assumed:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
x_{k} = A_{k} * x_{k-1} + q_{k-1}; q_{k-1} ~ N(0, Q_{k-1})
|
|
|
|
|
|
y_{k} = H_{k} * x_{k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Returns estimated filter distributions x_{k} ~ N(m_{k}, P(k))
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Current Features:
|
|
|
|
|
|
----------------------------------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
1) The function generaly do not modify the passed parameters. If
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it happens then it is an error. There are several exeprions: scalars
|
2016-03-15 17:01:05 +02:00
|
|
|
|
can be modified into a matrix, in some rare cases shapes of
|
2015-07-14 16:44:21 +03:00
|
|
|
|
the derivatives matrices may be changed, it is ignored for now.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
2) Copies of p_A, p_Q, index are created in memory to be used later
|
|
|
|
|
|
in smoother. References to copies are kept in "matrs_for_smoother"
|
|
|
|
|
|
return parameter.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
3) Function support "multiple time series mode" which means that exactly
|
|
|
|
|
|
the same State-Space model is used to filter several sets of measurements.
|
|
|
|
|
|
In this case third dimension of Y should include these state-space measurements
|
|
|
|
|
|
Log_likelihood and Grad_log_likelihood have the corresponding dimensions then.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
4) Calculation of Grad_log_likelihood is not supported if matrices A,Q,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
H, or R changes over time. (later may be changed)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
5) Measurement may include missing values. In this case update step is
|
|
|
|
|
|
not done for this measurement. (later may be changed)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Input:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
-----------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_A: scalar, square matrix, 3D array
|
|
|
|
|
|
A_{k} in the model. If matrix then A_{k} = A - constant.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
If it is 3D array then A_{k} = p_A[:,:, index[0,k]]
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_Q: scalar, square symmetric matrix, 3D array
|
|
|
|
|
|
Q_{k-1} in the model. If matrix then Q_{k-1} = Q - constant.
|
2015-07-14 16:44:21 +03:00
|
|
|
|
If it is 3D array then Q_{k-1} = p_Q[:,:, index[1,k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_H: scalar, matrix (measurement_dim, state_dim) , 3D array
|
|
|
|
|
|
H_{k} in the model. If matrix then H_{k} = H - constant.
|
|
|
|
|
|
If it is 3D array then H_{k} = p_Q[:,:, index[2,k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_R: scalar, square symmetric matrix, 3D array
|
|
|
|
|
|
R_{k} in the model. If matrix then R_{k} = R - constant.
|
|
|
|
|
|
If it is 3D array then R_{k} = p_R[:,:, index[3,k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Y: matrix or vector or 3D array
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Data. If Y is matrix then samples are along 0-th dimension and
|
2016-03-15 17:01:05 +02:00
|
|
|
|
features along the 1-st. If 3D array then third dimension
|
2015-07-14 16:44:21 +03:00
|
|
|
|
correspond to "multiple time series mode".
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
index: vector
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Which indices (on 3-rd dimension) from arrays p_A, p_Q,p_H, p_R to use
|
2016-03-15 17:01:05 +02:00
|
|
|
|
on every time step. If this parameter is None then it is assumed
|
2015-07-14 16:44:21 +03:00
|
|
|
|
that p_A, p_Q, p_H, p_R do not change over time and indices are not needed.
|
|
|
|
|
|
index[0,:] - correspond to A, index[1,:] - correspond to Q
|
|
|
|
|
|
index[2,:] - correspond to H, index[3,:] - correspond to R.
|
|
|
|
|
|
If index.shape[0] == 1, it is assumed that indides for all matrices
|
|
|
|
|
|
are the same.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
m_init: vector or matrix
|
|
|
|
|
|
Initial distribution mean. If None it is assumed to be zero.
|
|
|
|
|
|
For "multiple time series mode" it is matrix, second dimension of
|
|
|
|
|
|
which correspond to different time series. In regular case ("one
|
|
|
|
|
|
time series mode") it is a vector.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
P_init: square symmetric matrix or scalar
|
|
|
|
|
|
Initial covariance of the states. If the parameter is scalar
|
2016-03-15 17:01:05 +02:00
|
|
|
|
then it is assumed that initial covariance matrix is unit matrix
|
2015-04-07 18:20:11 +03:00
|
|
|
|
multiplied by this scalar. If None the unit matrix is used instead.
|
2015-07-14 16:44:21 +03:00
|
|
|
|
"multiple time series mode" does not affect it, since it does not
|
|
|
|
|
|
affect anything related to state variaces.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then "grad_calc_params" parameter must
|
2015-04-07 18:20:11 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
grad_params_no: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
If previous parameter is true, then this parameters gives the
|
|
|
|
|
|
total number of parameters in the gradient.
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_calc_params: dictionary
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Dictionary with derivatives of model matrices with respect
|
2015-07-14 16:44:21 +03:00
|
|
|
|
to parameters "dA", "dQ", "dH", "dR", "dm_init", "dP_init".
|
|
|
|
|
|
They can be None, in this case zero matrices (no dependence on parameters)
|
|
|
|
|
|
is assumed. If there is only one parameter then third dimension is
|
|
|
|
|
|
automatically added.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
M: (no_steps+1,state_dim) matrix or (no_steps+1,state_dim, time_series_no) 3D array
|
|
|
|
|
|
Filter estimates of the state means. In the extra step the initial
|
|
|
|
|
|
value is included. In the "multiple time series mode" third dimension
|
|
|
|
|
|
correspond to different timeseries.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P: (no_steps+1, state_dim, state_dim) 3D array
|
|
|
|
|
|
Filter estimates of the state covariances. In the extra step the initial
|
|
|
|
|
|
value is included.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
log_likelihood: double or (1, time_series_no) 3D array.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
If the parameter calc_log_likelihood was set to true, return
|
|
|
|
|
|
logarithm of marginal likelihood of the state-space model. If
|
2015-07-14 16:44:21 +03:00
|
|
|
|
the parameter was false, return None. In the "multiple time series mode" it is a vector
|
|
|
|
|
|
providing log_likelihood for each time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_log_likelihood: column vector or (grad_params_no, time_series_no) matrix
|
|
|
|
|
|
If calc_grad_log_likelihood is true, return gradient of log likelihood
|
2016-03-15 17:01:05 +02:00
|
|
|
|
with respect to parameters. It returns it column wise, so in
|
|
|
|
|
|
"multiple time series mode" gradients for each time series is in the
|
2015-07-14 16:44:21 +03:00
|
|
|
|
corresponding column.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
matrs_for_smoother: dict
|
|
|
|
|
|
Dictionary with model functions for smoother. The intrinsic model
|
|
|
|
|
|
functions are computed in this functions and they are returned to
|
|
|
|
|
|
use in smoother for convenience. They are: 'p_a', 'p_f_A', 'p_f_Q'
|
|
|
|
|
|
The dictionary contains the same fields.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Parameters checking ->
|
2016-03-15 17:01:05 +02:00
|
|
|
|
# index
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_A = np.atleast_1d(p_A)
|
|
|
|
|
|
p_Q = np.atleast_1d(p_Q)
|
|
|
|
|
|
p_H = np.atleast_1d(p_H)
|
|
|
|
|
|
p_R = np.atleast_1d(p_R)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Reshape and check measurements:
|
|
|
|
|
|
Y.shape, old_Y_shape = cls._reshape_input_data(Y.shape)
|
|
|
|
|
|
measurement_dim = Y.shape[1]
|
2015-09-04 19:44:19 +03:00
|
|
|
|
time_series_no = Y.shape[2] # multiple time series mode
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if ((len(p_A.shape) == 3) and (len(p_A.shape[2]) != 1)) or\
|
|
|
|
|
|
((len(p_Q.shape) == 3) and (len(p_Q.shape[2]) != 1)) or\
|
|
|
|
|
|
((len(p_H.shape) == 3) and (len(p_H.shape[2]) != 1)) or\
|
|
|
|
|
|
((len(p_R.shape) == 3) and (len(p_R.shape[2]) != 1)):
|
|
|
|
|
|
model_matrices_chage_with_time = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
model_matrices_chage_with_time = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Check index
|
|
|
|
|
|
old_index_shape = None
|
|
|
|
|
|
if index is None:
|
|
|
|
|
|
if (len(p_A.shape) == 3) or (len(p_Q.shape) == 3) or\
|
|
|
|
|
|
(len(p_H.shape) == 3) or (len(p_R.shape) == 3):
|
|
|
|
|
|
raise ValueError("Parameter index can not be None for time varying matrices (third dimension is present)")
|
|
|
|
|
|
else: # matrices do not change in time, so form dummy zero indices.
|
|
|
|
|
|
index = np.zeros((1,Y.shape[0]))
|
|
|
|
|
|
else:
|
|
|
|
|
|
if len(index.shape) == 1:
|
|
|
|
|
|
index.shape = (1,index.shape[0])
|
|
|
|
|
|
old_index_shape = (index.shape[0],)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if (index.shape[1] != Y.shape[0]):
|
2015-07-14 16:44:21 +03:00
|
|
|
|
raise ValueError("Number of measurements must be equal the number of A_{k}, Q_{k}, H_{k}, R_{k}")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if (index.shape[0] == 1):
|
|
|
|
|
|
A_time_var_index = 0; Q_time_var_index = 0
|
|
|
|
|
|
H_time_var_index = 0; R_time_var_index = 0
|
2015-07-14 16:44:21 +03:00
|
|
|
|
elif (index.shape[0] == 4):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
A_time_var_index = 0; Q_time_var_index = 1
|
2015-07-14 16:44:21 +03:00
|
|
|
|
H_time_var_index = 2; R_time_var_index = 3
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError("First Dimension of index must be either 1 or 4.")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
state_dim = p_A.shape[0]
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Check and make right shape for model matrices. On exit they all are 3 dimensional. Last dimension
|
|
|
|
|
|
# correspond to change in time.
|
|
|
|
|
|
(p_A, old_A_shape) = cls._check_SS_matrix(p_A, state_dim, measurement_dim, which='A')
|
|
|
|
|
|
(p_Q, old_Q_shape) = cls._check_SS_matrix(p_Q, state_dim, measurement_dim, which='Q')
|
|
|
|
|
|
(p_H, old_H_shape) = cls._check_SS_matrix(p_H, state_dim, measurement_dim, which='H')
|
|
|
|
|
|
(p_R, old_R_shape) = cls._check_SS_matrix(p_R, state_dim, measurement_dim, which='R')
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# m_init
|
|
|
|
|
|
if m_init is None:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_init = np.zeros((state_dim, time_series_no))
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_init = np.atleast_2d(m_init).T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# P_init
|
|
|
|
|
|
if P_init is None:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
P_init = np.eye(state_dim)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
elif not isinstance(P_init, collections.Iterable): #scalar
|
|
|
|
|
|
P_init = P_init*np.eye(state_dim)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if p_kalman_filter_type not in ('regular', 'svd'):
|
|
|
|
|
|
raise ValueError("Kalman filer type neither 'regular nor 'svd'.")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Functions to pass to the kalman_filter algorithm:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Parameters:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# k - number of Kalman filter iteration
|
|
|
|
|
|
# m - vector for calculating matrices. Required for EKF. Not used here.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
c_p_A = p_A.copy() # create a copy because this object is passed to the smoother
|
2016-03-08 17:56:20 +02:00
|
|
|
|
c_p_Q = p_Q.copy() # create a copy because this object is passed to the smoother
|
2015-07-14 16:44:21 +03:00
|
|
|
|
c_index = index.copy() # create a copy because this object is passed to the smoother
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if model_matrices_chage_with_time:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError("When computing likelihood gradient A and Q can not change over time.")
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dA = cls._check_grad_state_matrices(grad_calc_params.get('dA'), state_dim, grad_params_no, which = 'dA')
|
|
|
|
|
|
dQ = cls._check_grad_state_matrices(grad_calc_params.get('dQ'), state_dim, grad_params_no, which = 'dQ')
|
|
|
|
|
|
dH = cls._check_grad_measurement_matrices(grad_calc_params.get('dH'), state_dim, grad_params_no, measurement_dim, which = 'dH')
|
|
|
|
|
|
dR = cls._check_grad_measurement_matrices(grad_calc_params.get('dR'), state_dim, grad_params_no, measurement_dim, which = 'dR')
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dm_init = grad_calc_params.get('dm_init')
|
|
|
|
|
|
if dm_init is None:
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# multiple time series mode. Keep grad_params always as a last dimension
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm_init = np.zeros((state_dim, time_series_no, grad_params_no))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dP_init = grad_calc_params.get('dP_init')
|
|
|
|
|
|
if dP_init is None:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dP_init = np.zeros((state_dim,state_dim,grad_params_no))
|
2015-10-08 15:30:34 +03:00
|
|
|
|
else:
|
|
|
|
|
|
dA = None
|
|
|
|
|
|
dQ = None
|
|
|
|
|
|
dH = None
|
|
|
|
|
|
dR = None
|
|
|
|
|
|
dm_init = None
|
|
|
|
|
|
dP_init = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dynamic_callables = Std_Dynamic_Callables_Class(c_p_A, A_time_var_index, c_p_Q, c_index, Q_time_var_index, 20, dA, dQ)
|
|
|
|
|
|
measurement_callables = Std_Measurement_Callables_Class(p_H, H_time_var_index, p_R, index, R_time_var_index, 20, dH, dR)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
(M, P,log_likelihood, grad_log_likelihood, dynamic_callables) = \
|
|
|
|
|
|
cls._kalman_algorithm_raw(state_dim, dynamic_callables,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
measurement_callables, Y, m_init,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
P_init, p_kalman_filter_type = p_kalman_filter_type,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
grad_params_no=grad_params_no,
|
|
|
|
|
|
dm_init=dm_init, dP_init=dP_init)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# restore shapes so that input parameters are unchenged
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_index_shape is not None:
|
|
|
|
|
|
index.shape = old_index_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_Y_shape is not None:
|
|
|
|
|
|
Y.shape = old_Y_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_A_shape is not None:
|
|
|
|
|
|
p_A.shape = old_A_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_Q_shape is not None:
|
|
|
|
|
|
p_Q.shape = old_Q_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_H_shape is not None:
|
|
|
|
|
|
p_H.shape = old_H_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_R_shape is not None:
|
|
|
|
|
|
p_R.shape = old_R_shape
|
|
|
|
|
|
# Return values
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return (M, P,log_likelihood, grad_log_likelihood, dynamic_callables)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2015-04-07 18:20:11 +03:00
|
|
|
|
def extended_kalman_filter(cls,p_state_dim, p_a, p_f_A, p_f_Q, p_h, p_f_H, p_f_R, Y, m_init=None,
|
|
|
|
|
|
P_init=None,calc_log_likelihood=False):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Extended Kalman Filter
|
|
|
|
|
|
|
|
|
|
|
|
Input:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
-----------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_state_dim: integer
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_a: if None - the function from the linear model is assumed. No non-
|
|
|
|
|
|
linearity in the dynamic is assumed.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
function (k, x_{k-1}, A_{k}). Dynamic function.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
k: (iteration number),
|
2015-04-07 18:20:11 +03:00
|
|
|
|
x_{k-1}: (previous state)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
x_{k}: Jacobian matrices of f_a. In the linear case it is exactly A_{k}.
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_A: matrix - in this case function which returns this matrix is assumed.
|
|
|
|
|
|
Look at this parameter description in kalman_filter function.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_a.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
k: (iteration number),
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_Q: matrix. In this case function which returns this matrix is asumed.
|
|
|
|
|
|
Look at this parameter description in kalman_filter function.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
function (k). Returns noise matrix of dynamic model on iteration k.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
k: (iteration number).
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_h: if None - the function from the linear measurement model is assumed.
|
|
|
|
|
|
No nonlinearity in the measurement is assumed.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
function (k, x_{k}, H_{k}). Measurement function.
|
|
|
|
|
|
k: (iteration number),
|
|
|
|
|
|
x_{k}: (current state)
|
|
|
|
|
|
H_{k}: Jacobian matrices of f_h. In the linear case it is exactly H_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_H: matrix - in this case function which returns this matrix is assumed.
|
|
|
|
|
|
function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k: (iteration number),
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_R: matrix. In this case function which returns this matrix is asumed.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function (k). Returns noise matrix of measurement equation
|
2015-04-07 18:20:11 +03:00
|
|
|
|
on iteration k.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
k: (iteration number).
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Y: matrix or vector
|
|
|
|
|
|
Data. If Y is matrix then samples are along 0-th dimension and
|
2016-03-15 17:01:05 +02:00
|
|
|
|
features along the 1-st. May have missing values.
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_mean: vector
|
|
|
|
|
|
Initial distribution mean. If None it is assumed to be zero
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
P_init: square symmetric matrix or scalar
|
|
|
|
|
|
Initial covariance of the states. If the parameter is scalar
|
2016-03-15 17:01:05 +02:00
|
|
|
|
then it is assumed that initial covariance matrix is unit matrix
|
2015-04-07 18:20:11 +03:00
|
|
|
|
multiplied by this scalar. If None the unit matrix is used instead.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Y
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Y.shape, old_Y_shape = cls._reshape_input_data(Y.shape)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# m_init
|
|
|
|
|
|
if m_init is None:
|
|
|
|
|
|
m_init = np.zeros((p_state_dim,1))
|
|
|
|
|
|
else:
|
|
|
|
|
|
m_init = np.atleast_2d(m_init).T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# P_init
|
|
|
|
|
|
if P_init is None:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
P_init = np.eye(p_state_dim)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
elif not isinstance(P_init, collections.Iterable): #scalar
|
|
|
|
|
|
P_init = P_init*np.eye(p_state_dim)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if p_a is None:
|
|
|
|
|
|
p_a = lambda k,m,A: np.dot(A, m)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
old_A_shape = None
|
|
|
|
|
|
if not isinstance(p_f_A, types.FunctionType): # not a function but array
|
|
|
|
|
|
p_f_A = np.atleast_1d(p_f_A)
|
|
|
|
|
|
(p_A, old_A_shape) = cls._check_A_matrix(p_f_A)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_A = lambda k, m, P: p_A[:,:, 0] # make function
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
|
|
|
|
|
if p_f_A(1, m_init, P_init).shape[0] != m_init.shape[0]:
|
|
|
|
|
|
raise ValueError("p_f_A function returns matrix of wrong size")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
old_Q_shape = None
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if not isinstance(p_f_Q, types.FunctionType): # not a function but array
|
|
|
|
|
|
p_f_Q = np.atleast_1d(p_f_Q)
|
|
|
|
|
|
(p_Q, old_Q_shape) = cls._check_Q_matrix(p_f_Q)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_Q = lambda k: p_Q[:,:, 0] # make function
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
|
|
|
|
|
if p_f_Q(1).shape[0] != m_init.shape[0]:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError("p_f_Q function returns matrix of wrong size")
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if p_h is None:
|
|
|
|
|
|
lambda k,m,H: np.dot(H, m)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
old_H_shape = None
|
|
|
|
|
|
if not isinstance(p_f_H, types.FunctionType): # not a function but array
|
|
|
|
|
|
p_f_H = np.atleast_1d(p_f_H)
|
|
|
|
|
|
(p_H, old_H_shape) = cls._check_H_matrix(p_f_H)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_H = lambda k, m, P: p_H # make function
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
|
|
|
|
|
if p_f_H(1, m_init, P_init).shape[0] != Y.shape[1]:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError("p_f_H function returns matrix of wrong size")
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
|
|
|
|
|
old_R_shape = None
|
|
|
|
|
|
if not isinstance(p_f_R, types.FunctionType): # not a function but array
|
|
|
|
|
|
p_f_R = np.atleast_1d(p_f_R)
|
|
|
|
|
|
(p_R, old_R_shape) = cls._check_H_matrix(p_f_R)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_R = lambda k: p_R # make function
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
|
|
|
|
|
if p_f_R(1).shape[0] != m_init.shape[0]:
|
|
|
|
|
|
raise ValueError("p_f_R function returns matrix of wrong size")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# class dynamic_callables_class(Dynamic_Model_Callables):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#
|
|
|
|
|
|
# Ak =
|
|
|
|
|
|
# Qk =
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class measurement_callables_class(R_handling_Class):
|
|
|
|
|
|
def __init__(self,R, index, R_time_var_index, unique_R_number):
|
|
|
|
|
|
super(measurement_callables_class,self).__init__(R, index, R_time_var_index, unique_R_number)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Hk = AddMethodToClass(f_H)
|
|
|
|
|
|
f_h = AddMethodToClass(f_hl)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
(M, P,log_likelihood, grad_log_likelihood) = cls._kalman_algorithm_raw(p_state_dim, p_a, p_f_A, p_f_Q, p_h, p_f_H, p_f_R, Y, m_init,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
P_init, calc_log_likelihood,
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_grad_log_likelihood=False, grad_calc_params=None)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_Y_shape is not None:
|
|
|
|
|
|
Y.shape = old_Y_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_A_shape is not None:
|
|
|
|
|
|
p_A.shape = old_A_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_Q_shape is not None:
|
|
|
|
|
|
p_Q.shape = old_Q_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_H_shape is not None:
|
|
|
|
|
|
p_H.shape = old_H_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if old_R_shape is not None:
|
|
|
|
|
|
p_R.shape = old_R_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return (M, P)
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@classmethod
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def _kalman_algorithm_raw(cls,state_dim, p_dynamic_callables, p_measurement_callables, Y, m_init,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
P_init, p_kalman_filter_type='regular',
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_log_likelihood=False,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
calc_grad_log_likelihood=False, grad_params_no=None,
|
|
|
|
|
|
dm_init=None, dP_init=None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
General nonlinear filtering algorithm for inference in the state-space
|
2015-04-07 18:20:11 +03:00
|
|
|
|
model:
|
|
|
|
|
|
|
|
|
|
|
|
x_{k} = f_a(k, x_{k-1}, A_{k}) + q_{k-1}; q_{k-1} ~ N(0, Q_{k-1})
|
|
|
|
|
|
y_{k} = f_h(k, x_{k}, H_{k}) + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Returns estimated filter distributions x_{k} ~ N(m_{k}, P(k))
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Current Features:
|
|
|
|
|
|
----------------------------------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
1) Function support "multiple time series mode" which means that exactly
|
|
|
|
|
|
the same State-Space model is used to filter several sets of measurements.
|
|
|
|
|
|
In this case third dimension of Y should include these state-space measurements
|
|
|
|
|
|
Log_likelihood and Grad_log_likelihood have the corresponding dimensions then.
|
|
|
|
|
|
|
|
|
|
|
|
2) Measurement may include missing values. In this case update step is
|
|
|
|
|
|
not done for this measurement. (later may be changed)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Input:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
-----------------
|
2015-07-14 16:44:21 +03:00
|
|
|
|
state_dim: int
|
|
|
|
|
|
Demensionality of the states
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_a: function (k, x_{k-1}, A_{k}). Dynamic function.
|
|
|
|
|
|
k (iteration number),
|
|
|
|
|
|
x_{k-1}
|
2015-07-14 16:44:21 +03:00
|
|
|
|
A_{k} Jacobian matrices of f_a. In the linear case it is exactly A_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_A: function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_a.
|
|
|
|
|
|
k (iteration number),
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_Q: function (k). Returns noise matrix of dynamic model on iteration k.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
k (iteration number).
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_h: function (k, x_{k}, H_{k}). Measurement function.
|
|
|
|
|
|
k (iteration number),
|
|
|
|
|
|
x_{k}
|
|
|
|
|
|
H_{k} Jacobian matrices of f_h. In the linear case it is exactly H_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_H: function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k (iteration number),
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_R: function (k). Returns noise matrix of measurement equation
|
2015-04-07 18:20:11 +03:00
|
|
|
|
on iteration k.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
k (iteration number).
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Y: matrix or vector or 3D array
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Data. If Y is matrix then samples are along 0-th dimension and
|
2016-03-15 17:01:05 +02:00
|
|
|
|
features along the 1-st. If 3D array then third dimension
|
2015-07-14 16:44:21 +03:00
|
|
|
|
correspond to "multiple time series mode".
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
m_init: vector or matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Initial distribution mean. For "multiple time series mode"
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it is matrix, second dimension of which correspond to different
|
2016-03-15 17:01:05 +02:00
|
|
|
|
time series. In regular case ("one time series mode") it is a
|
2015-07-14 16:44:21 +03:00
|
|
|
|
vector.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
P_init: matrix or scalar
|
|
|
|
|
|
Initial covariance of the states. Must be not None
|
2015-07-14 16:44:21 +03:00
|
|
|
|
"multiple time series mode" does not affect it, since it does not
|
|
|
|
|
|
affect anything related to state variaces.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
p_kalman_filter_type: string
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then the next parameter must
|
2015-04-07 18:20:11 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_calc_params: dictionary
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Dictionary with derivatives of model matrices with respect
|
2015-07-14 16:44:21 +03:00
|
|
|
|
to parameters "dA", "dQ", "dH", "dR", "dm_init", "dP_init".
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
M: (no_steps+1,state_dim) matrix or (no_steps+1,state_dim, time_series_no) 3D array
|
|
|
|
|
|
Filter estimates of the state means. In the extra step the initial
|
|
|
|
|
|
value is included. In the "multiple time series mode" third dimension
|
|
|
|
|
|
correspond to different timeseries.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P: (no_steps+1, state_dim, state_dim) 3D array
|
|
|
|
|
|
Filter estimates of the state covariances. In the extra step the initial
|
|
|
|
|
|
value is included.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
log_likelihood: double or (1, time_series_no) 3D array.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
If the parameter calc_log_likelihood was set to true, return
|
|
|
|
|
|
logarithm of marginal likelihood of the state-space model. If
|
2015-07-14 16:44:21 +03:00
|
|
|
|
the parameter was false, return None. In the "multiple time series mode" it is a vector
|
|
|
|
|
|
providing log_likelihood for each time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_log_likelihood: column vector or (grad_params_no, time_series_no) matrix
|
|
|
|
|
|
If calc_grad_log_likelihood is true, return gradient of log likelihood
|
2016-03-15 17:01:05 +02:00
|
|
|
|
with respect to parameters. It returns it column wise, so in
|
|
|
|
|
|
"multiple time series mode" gradients for each time series is in the
|
2015-07-14 16:44:21 +03:00
|
|
|
|
corresponding column.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
steps_no = Y.shape[0] # number of steps in the Kalman Filter
|
2015-09-04 19:44:19 +03:00
|
|
|
|
time_series_no = Y.shape[2] # multiple time series mode
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Allocate space for results
|
|
|
|
|
|
# Mean estimations. Initial values will be included
|
2015-09-04 19:44:19 +03:00
|
|
|
|
M = np.empty(((steps_no+1),state_dim,time_series_no))
|
|
|
|
|
|
M[0,:,:] = m_init # Initialize mean values
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Variance estimations. Initial values will be included
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P = np.empty(((steps_no+1),state_dim,state_dim))
|
2015-10-08 15:30:34 +03:00
|
|
|
|
P_init = 0.5*( P_init + P_init.T) # symmetrize initial covariance. In some ustable cases this is uiseful
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P[0,:,:] = P_init # Initialize initial covariance matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2016-03-15 17:01:05 +02:00
|
|
|
|
(U,S,Vh) = sp.linalg.svd( P_init,full_matrices=False, compute_uv=True,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
overwrite_a=False,check_finite=True)
|
|
|
|
|
|
S[ (S==0) ] = 1e-17 # allows to run algorithm for singular initial variance
|
|
|
|
|
|
P_upd = (P_init, S,U)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
log_likelihood = 0 if calc_log_likelihood else None
|
|
|
|
|
|
grad_log_likelihood = 0 if calc_grad_log_likelihood else None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
#setting initial values for derivatives update
|
|
|
|
|
|
dm_upd = dm_init
|
|
|
|
|
|
dP_upd = dP_init
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Main loop of the Kalman filter
|
|
|
|
|
|
for k in range(0,steps_no):
|
|
|
|
|
|
# In this loop index for new estimations is (k+1), old - (k)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# This happened because initial values are stored at 0-th index.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
prev_mean = M[k,:,:] # mean from the previous step
|
2015-07-14 16:44:21 +03:00
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2015-10-08 15:30:34 +03:00
|
|
|
|
m_pred, P_pred, dm_pred, dP_pred = \
|
|
|
|
|
|
cls._kalman_prediction_step_SVD(k, prev_mean ,P_upd, p_dynamic_callables,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
p_dm = dm_upd, p_dP = dP_upd)
|
|
|
|
|
|
else:
|
|
|
|
|
|
m_pred, P_pred, dm_pred, dP_pred = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_prediction_step(k, prev_mean ,P[k,:,:], p_dynamic_callables,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
|
|
|
|
|
p_dm = dm_upd, p_dP = dP_upd )
|
|
|
|
|
|
|
|
|
|
|
|
k_measurment = Y[k,:,:]
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if (np.any(np.isnan(k_measurment)) == False):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2015-10-08 15:30:34 +03:00
|
|
|
|
m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_update_step_SVD(k, m_pred , P_pred, p_measurement_callables,
|
|
|
|
|
|
k_measurment, calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
p_dm = dm_pred, p_dP = dP_pred )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
# cls._kalman_update_step(k, m_pred , P_pred[0], f_h, f_H, p_R.f_R, k_measurment,
|
|
|
|
|
|
# calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
# calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# p_dm = dm_pred, p_dP = dP_pred, grad_calc_params_2 = (dH, dR))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#
|
|
|
|
|
|
# (U,S,Vh) = sp.linalg.svd( P_upd,full_matrices=False, compute_uv=True,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# overwrite_a=False,check_finite=True)
|
|
|
|
|
|
# P_upd = (P_upd, S,U)
|
|
|
|
|
|
else:
|
|
|
|
|
|
m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_update_step(k, m_pred , P_pred, p_measurement_callables, k_measurment,
|
|
|
|
|
|
calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
p_dm = dm_pred, p_dP = dP_pred )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
else:
|
|
|
|
|
|
# if k_measurment.shape != (1,1):
|
|
|
|
|
|
# raise ValueError("Nan measurements are currently not supported for \
|
|
|
|
|
|
# multidimensional output and multiple time series.")
|
|
|
|
|
|
# else:
|
|
|
|
|
|
# m_upd = m_pred; P_upd = P_pred; dm_upd = dm_pred; dP_upd = dP_pred
|
|
|
|
|
|
# log_likelihood_update = 0.0;
|
|
|
|
|
|
# d_log_likelihood_update = 0.0;
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if not np.all(np.isnan(k_measurment)):
|
|
|
|
|
|
raise ValueError("""Nan measurements are currently not supported if
|
|
|
|
|
|
they are intermixed with not NaN measurements""")
|
|
|
|
|
|
else:
|
|
|
|
|
|
m_upd = m_pred; P_upd = P_pred; dm_upd = dm_pred; dP_upd = dP_pred
|
|
|
|
|
|
if calc_log_likelihood:
|
|
|
|
|
|
log_likelihood_update = np.zeros((time_series_no,))
|
|
|
|
|
|
if calc_grad_log_likelihood:
|
|
|
|
|
|
d_log_likelihood_update = np.zeros((grad_params_no,time_series_no))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_log_likelihood:
|
|
|
|
|
|
log_likelihood += log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
|
|
|
|
|
grad_log_likelihood += d_log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
M[k+1,:,:] = m_upd # separate mean value for each time series
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2015-10-08 15:30:34 +03:00
|
|
|
|
P[k+1,:,:] = P_upd[0]
|
|
|
|
|
|
else:
|
|
|
|
|
|
P[k+1,:,:] = P_upd
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# !!!Print statistics! Print sizes of matrices
|
|
|
|
|
|
# !!!Print statistics! Print iteration time base on another boolean variable
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return (M, P, log_likelihood, grad_log_likelihood, p_dynamic_callables.reset(False))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _kalman_prediction_step(k, p_m , p_P, p_dyn_model_callable, calc_grad_log_likelihood=False,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_dm = None, p_dP = None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Desctrete prediction function
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
k:int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Iteration No. Starts at 0. Total number of iterations equal to the
|
2015-04-07 18:20:11 +03:00
|
|
|
|
number of measurements.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_m: matrix of size (state_dim, time_series_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean value from the previous step. For "multiple time series mode"
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it is matrix, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_P:
|
|
|
|
|
|
Covariance matrix from the previous step.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_dyn_model_callable: class
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then the next parameter must
|
2015-04-07 18:20:11 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_dm: 3D array (state_dim, time_series_no, parameters_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean derivatives from the previous step. For "multiple time series mode"
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it is 3D array, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_dP: 3D array (state_dim, state_dim, parameters_no)
|
|
|
|
|
|
Mean derivatives from the previous step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
----------------------------
|
|
|
|
|
|
m_pred, P_pred, dm_pred, dP_pred: metrices, 3D objects
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Results of the prediction steps.
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# index correspond to values from previous iteration.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
A = p_dyn_model_callable.Ak(k,p_m,p_P) # state transition matrix (or Jacobian)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Q = p_dyn_model_callable.Qk(k) # state noise matrix
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Prediction step ->
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_pred = p_dyn_model_callable.f_a(k, p_m, A) # predicted mean
|
2015-04-07 18:20:11 +03:00
|
|
|
|
P_pred = A.dot(p_P).dot(A.T) + Q # predicted variance
|
|
|
|
|
|
# Prediction step <-
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dA_all_params = p_dyn_model_callable.dAk(k) # derivatives of A wrt parameters
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dQ_all_params = p_dyn_model_callable.dQk(k) # derivatives of Q wrt parameters
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
param_number = p_dP.shape[2]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# p_dm, p_dP - derivatives form the previoius step
|
|
|
|
|
|
dm_pred = np.empty(p_dm.shape)
|
|
|
|
|
|
dP_pred = np.empty(p_dP.shape)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
for j in range(param_number):
|
|
|
|
|
|
dA = dA_all_params[:,:,j]
|
|
|
|
|
|
dQ = dQ_all_params[:,:,j]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dP = p_dP[:,:,j]
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm = p_dm[:,:,j]
|
|
|
|
|
|
dm_pred[:,:,j] = np.dot(dA, p_m) + np.dot(A, dm)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# prediction step derivatives for current parameter:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dP_pred[:,:,j] = np.dot( dA ,np.dot(p_P, A.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dP_pred[:,:,j] += dP_pred[:,:,j].T
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dP_pred[:,:,j] += np.dot( A ,np.dot(dP, A.T)) + dQ
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-08 19:44:19 +03:00
|
|
|
|
dP_pred[:,:,j] = 0.5*(dP_pred[:,:,j] + dP_pred[:,:,j].T) #symmetrize
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
|
|
|
|
|
dm_pred = None
|
|
|
|
|
|
dP_pred = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return m_pred, P_pred, dm_pred, dP_pred
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _kalman_prediction_step_SVD(k, p_m , p_P, p_dyn_model_callable, calc_grad_log_likelihood=False,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_dm = None, p_dP = None):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Desctrete prediction function
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
k:int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Iteration No. Starts at 0. Total number of iterations equal to the
|
2015-08-10 19:40:39 +03:00
|
|
|
|
number of measurements.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_m: matrix of size (state_dim, time_series_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean value from the previous step. For "multiple time series mode"
|
2015-08-10 19:40:39 +03:00
|
|
|
|
it is matrix, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_P: tuple (Prev_cov, S, V)
|
|
|
|
|
|
Covariance matrix from the previous step and its SVD decomposition.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Prev_cov = V * S * V.T The tuple is (Prev_cov, S, V)
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_dyn_model_callable: object
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then the next parameter must
|
2015-08-10 19:40:39 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_dm: 3D array (state_dim, time_series_no, parameters_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean derivatives from the previous step. For "multiple time series mode"
|
2015-08-10 19:40:39 +03:00
|
|
|
|
it is 3D array, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_dP: 3D array (state_dim, state_dim, parameters_no)
|
|
|
|
|
|
Mean derivatives from the previous step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
----------------------------
|
|
|
|
|
|
m_pred, P_pred, dm_pred, dP_pred: metrices, 3D objects
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Results of the prediction steps.
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# covariance from the previous step and its SVD decomposition
|
|
|
|
|
|
# p_prev_cov = v * S * V.T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Prev_cov, S_old, V_old = p_P
|
|
|
|
|
|
#p_prev_cov_tst = np.dot(p_V, (p_S * p_V).T) # reconstructed covariance from the previous step
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# index correspond to values from previous iteration.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
A = p_dyn_model_callable.Ak(k,p_m,Prev_cov) # state transition matrix (or Jacobian)
|
|
|
|
|
|
Q = p_dyn_model_callable.Qk(k) # state noise matrx. This is necessary for the square root calculation (next step)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Q_sr = p_dyn_model_callable.Q_srk(k)
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# Prediction step ->
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_pred = p_dyn_model_callable.f_a(k, p_m, A) # predicted mean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# coavariance prediction have changed:
|
|
|
|
|
|
svd_1_matr = np.vstack( ( (np.sqrt(S_old)* np.dot(A,V_old)).T , Q_sr.T) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
(U,S,Vh) = sp.linalg.svd( svd_1_matr,full_matrices=False, compute_uv=True,
|
2015-08-10 19:40:39 +03:00
|
|
|
|
overwrite_a=False,check_finite=True)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# predicted variance computed by the regular method. For testing
|
|
|
|
|
|
#P_pred_tst = A.dot(Prev_cov).dot(A.T) + Q
|
|
|
|
|
|
V_new = Vh.T
|
|
|
|
|
|
S_new = S**2
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
P_pred = np.dot(V_new * S_new, V_new.T) # prediction covariance
|
|
|
|
|
|
P_pred = (P_pred, S_new, Vh.T)
|
|
|
|
|
|
# Prediction step <-
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# derivatives
|
|
|
|
|
|
if calc_grad_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dA_all_params = p_dyn_model_callable.dAk(k) # derivatives of A wrt parameters
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dQ_all_params = p_dyn_model_callable.dQk(k) # derivatives of Q wrt parameters
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
param_number = p_dP.shape[2]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# p_dm, p_dP - derivatives form the previoius step
|
|
|
|
|
|
dm_pred = np.empty(p_dm.shape)
|
|
|
|
|
|
dP_pred = np.empty(p_dP.shape)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
for j in range(param_number):
|
|
|
|
|
|
dA = dA_all_params[:,:,j]
|
|
|
|
|
|
dQ = dQ_all_params[:,:,j]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#dP = p_dP[:,:,j]
|
|
|
|
|
|
#dm = p_dm[:,:,j]
|
|
|
|
|
|
dm_pred[:,:,j] = np.dot(dA, p_m) + np.dot(A, p_dm[:,:,j])
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# prediction step derivatives for current parameter:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
dP_pred[:,:,j] = np.dot( dA ,np.dot(Prev_cov, A.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dP_pred[:,:,j] += dP_pred[:,:,j].T
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_pred[:,:,j] += np.dot( A ,np.dot(p_dP[:,:,j], A.T)) + dQ
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
dP_pred[:,:,j] = 0.5*(dP_pred[:,:,j] + dP_pred[:,:,j].T) #symmetrize
|
|
|
|
|
|
else:
|
|
|
|
|
|
dm_pred = None
|
|
|
|
|
|
dP_pred = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return m_pred, P_pred, dm_pred, dP_pred
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@staticmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def _kalman_update_step(k, p_m , p_P, p_meas_model_callable, measurement, calc_log_likelihood= False,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
calc_grad_log_likelihood=False, p_dm = None, p_dP = None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Input:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
k: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Iteration No. Starts at 0. Total number of iterations equal to the
|
2015-04-07 18:20:11 +03:00
|
|
|
|
number of measurements.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
m_P: matrix of size (state_dim, time_series_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean value from the previous step. For "multiple time series mode"
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it is matrix, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_P:
|
2015-08-10 19:40:39 +03:00
|
|
|
|
Covariance matrix from the prediction step.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_meas_model_callable: object
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
measurement: (measurement_dim, time_series_no) matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
One measurement used on the current update step. For
|
|
|
|
|
|
"multiple time series mode" it is matrix, second dimension of
|
2015-07-14 16:44:21 +03:00
|
|
|
|
which correspond to different time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then the next parameter must
|
2015-07-14 16:44:21 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_dm: 3D array (state_dim, time_series_no, parameters_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean derivatives from the prediction step. For "multiple time series mode"
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it is 3D array, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_dP: array
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Covariance derivatives from the prediction step.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
----------------------------
|
|
|
|
|
|
m_upd, P_upd, dm_upd, dP_upd: metrices, 3D objects
|
|
|
|
|
|
Results of the prediction steps.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
log_likelihood_update: double or 1D array
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Update to the log_likelihood from this step
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
d_log_likelihood_update: (grad_params_no, time_series_no) matrix
|
|
|
|
|
|
Update to the gradient of log_likelihood, "multiple time series mode"
|
|
|
|
|
|
adds extra columns to the gradient.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
"""
|
2015-10-08 15:30:34 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
m_pred = p_m # from prediction step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
P_pred = p_P # from prediction step
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
H = p_meas_model_callable.Hk(k, m_pred, P_pred)
|
|
|
|
|
|
R = p_meas_model_callable.Rk(k)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
time_series_no = p_m.shape[1] # number of time serieses
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
log_likelihood_update=None; dm_upd=None; dP_upd=None; d_log_likelihood_update=None
|
|
|
|
|
|
# Update step (only if there is data)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#if not np.any(np.isnan(measurement)): # TODO: if some dimensions are missing, do properly computations for other.
|
|
|
|
|
|
v = measurement-p_meas_model_callable.f_h(k, m_pred, H)
|
|
|
|
|
|
S = H.dot(P_pred).dot(H.T) + R
|
|
|
|
|
|
if measurement.shape[0]==1: # measurements are one dimensional
|
|
|
|
|
|
if (S < 0):
|
|
|
|
|
|
raise ValueError("Kalman Filter Update: S is negative step %i" % k )
|
|
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
K = P_pred.dot(H.T) / S
|
|
|
|
|
|
if calc_log_likelihood:
|
|
|
|
|
|
log_likelihood_update = -0.5 * ( np.log(2*np.pi) + np.log(S) +
|
|
|
|
|
|
v*v / S)
|
|
|
|
|
|
#log_likelihood_update = log_likelihood_update[0,0] # to make int
|
|
|
|
|
|
if np.any(np.isnan(log_likelihood_update)): # some member in P_pred is None.
|
|
|
|
|
|
raise ValueError("Nan values in likelihood update!")
|
|
|
|
|
|
LL = None; islower = None
|
|
|
|
|
|
else:
|
|
|
|
|
|
LL,islower = linalg.cho_factor(S)
|
|
|
|
|
|
K = linalg.cho_solve((LL,islower), H.dot(P_pred.T)).T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if calc_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
log_likelihood_update = -0.5 * ( v.shape[0]*np.log(2*np.pi) +
|
2015-09-04 19:44:19 +03:00
|
|
|
|
2*np.sum( np.log(np.diag(LL)) ) +\
|
|
|
|
|
|
np.sum((linalg.cho_solve((LL,islower),v)) * v, axis = 0) ) # diagonal of v.T*S^{-1}*v
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dm_pred_all_params = p_dm # derivativas of the prediction phase
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_pred_all_params = p_dP
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
param_number = p_dP.shape[2]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dH_all_params = p_meas_model_callable.dHk(k)
|
|
|
|
|
|
dR_all_params = p_meas_model_callable.dRk(k)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm_upd = np.empty(dm_pred_all_params.shape)
|
|
|
|
|
|
dP_upd = np.empty(dP_pred_all_params.shape)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# firts dimension parameter_no, second - time series number
|
|
|
|
|
|
d_log_likelihood_update = np.empty((param_number,time_series_no))
|
|
|
|
|
|
for param in range(param_number):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dH = dH_all_params[:,:,param]
|
|
|
|
|
|
dR = dR_all_params[:,:,param]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm_pred = dm_pred_all_params[:,:,param]
|
|
|
|
|
|
dP_pred = dP_pred_all_params[:,:,param]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# Terms in the likelihood derivatives
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dv = - np.dot( dH, m_pred) - np.dot( H, dm_pred)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dS = np.dot(dH, np.dot( P_pred, H.T))
|
|
|
|
|
|
dS += dS.T
|
|
|
|
|
|
dS += np.dot(H, np.dot( dP_pred, H.T)) + dR
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# TODO: maybe symmetrize dS
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#dm and dP for the next stem
|
|
|
|
|
|
if LL is not None: # the state vector is not a scalar
|
|
|
|
|
|
tmp1 = linalg.cho_solve((LL,islower), H).T
|
|
|
|
|
|
tmp2 = linalg.cho_solve((LL,islower), dH).T
|
|
|
|
|
|
tmp3 = linalg.cho_solve((LL,islower), dS).T
|
|
|
|
|
|
else: # the state vector is a scalar
|
|
|
|
|
|
tmp1 = H.T / S
|
|
|
|
|
|
tmp2 = dH.T / S
|
|
|
|
|
|
tmp3 = dS.T / S
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dK = np.dot( dP_pred, tmp1) + np.dot( P_pred, tmp2) - \
|
|
|
|
|
|
np.dot( P_pred, np.dot( tmp1, tmp3 ) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# terms required for the next step, save this for each parameter
|
|
|
|
|
|
dm_upd[:,:,param] = dm_pred + np.dot(dK, v) + np.dot(K, dv)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
dP_upd[:,:,param] = -np.dot(dK, np.dot(S, K.T))
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_upd[:,:,param] += dP_upd[:,:,param].T
|
|
|
|
|
|
dP_upd[:,:,param] += dP_pred - np.dot(K , np.dot( dS, K.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_upd[:,:,param] = 0.5*(dP_upd[:,:,param] + dP_upd[:,:,param].T) #symmetrize
|
|
|
|
|
|
# computing the likelihood change for each parameter:
|
|
|
|
|
|
if LL is not None: # the state vector is not 1D
|
|
|
|
|
|
#tmp4 = linalg.cho_solve((LL,islower), dv)
|
|
|
|
|
|
tmp5 = linalg.cho_solve((LL,islower), v)
|
|
|
|
|
|
else: # the state vector is a scalar
|
|
|
|
|
|
#tmp4 = dv / S
|
|
|
|
|
|
tmp5 = v / S
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
d_log_likelihood_update[param,:] = -(0.5*np.sum(np.diag(tmp3)) + \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
np.sum(tmp5*dv, axis=0) - 0.5 * np.sum(tmp5 * np.dot(dS, tmp5), axis=0) )
|
|
|
|
|
|
# Before
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#d_log_likelihood_update[param,0] = -(0.5*np.sum(np.diag(tmp3)) + \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#np.dot(tmp5.T, dv) - 0.5 * np.dot(tmp5.T ,np.dot(dS, tmp5)) )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# Compute the actual updates for mean and variance of the states.
|
|
|
|
|
|
m_upd = m_pred + K.dot( v )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# Covariance update and ensure it is symmetric
|
|
|
|
|
|
P_upd = K.dot(S).dot(K.T)
|
|
|
|
|
|
P_upd = 0.5*(P_upd + P_upd.T)
|
|
|
|
|
|
P_upd = P_pred - P_upd# this update matrix is symmetric
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
@staticmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def _kalman_update_step_SVD(k, p_m , p_P, p_meas_model_callable, measurement, calc_log_likelihood= False,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
calc_grad_log_likelihood=False, p_dm = None, p_dP = None):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Input:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
k: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Iteration No. Starts at 0. Total number of iterations equal to the
|
2015-08-10 19:40:39 +03:00
|
|
|
|
number of measurements.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
m_P: matrix of size (state_dim, time_series_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean value from the previous step. For "multiple time series mode"
|
2015-08-10 19:40:39 +03:00
|
|
|
|
it is matrix, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_P: tuple (P_pred, S, V)
|
|
|
|
|
|
Covariance matrix from the prediction step and its SVD decomposition.
|
|
|
|
|
|
P_pred = V * S * V.T The tuple is (P_pred, S, V)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_h: function (k, x_{k}, H_{k}). Measurement function.
|
|
|
|
|
|
k (iteration number), starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
x_{k} state
|
2015-08-10 19:40:39 +03:00
|
|
|
|
H_{k} Jacobian matrices of f_h. In the linear case it is exactly H_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_f_H: function (k, m, P) return Jacobian of measurement function, it is
|
2015-08-10 19:40:39 +03:00
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_R: function (k). Returns noise matrix of measurement equation
|
2015-08-10 19:40:39 +03:00
|
|
|
|
on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_iRsr: function (k). Returns the square root of the noise matrix of
|
|
|
|
|
|
measurement equation on iteration k.
|
2015-08-10 19:40:39 +03:00
|
|
|
|
k (iteration number). starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
measurement: (measurement_dim, time_series_no) matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
One measurement used on the current update step. For
|
|
|
|
|
|
"multiple time series mode" it is matrix, second dimension of
|
2015-08-10 19:40:39 +03:00
|
|
|
|
which correspond to different time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then the next parameter must
|
2015-08-10 19:40:39 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_dm: 3D array (state_dim, time_series_no, parameters_no)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mean derivatives from the prediction step. For "multiple time series mode"
|
2015-08-10 19:40:39 +03:00
|
|
|
|
it is 3D array, second dimension of which correspond to different
|
|
|
|
|
|
time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_dP: array
|
|
|
|
|
|
Covariance derivatives from the prediction step.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
grad_calc_params_2: List or None
|
|
|
|
|
|
List with derivatives. The first component is 'f_dH' - function(k)
|
|
|
|
|
|
which returns the derivative of H. The second element is 'f_dR'
|
|
|
|
|
|
- function(k). Function which returns the derivative of R.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
----------------------------
|
|
|
|
|
|
m_upd, P_upd, dm_upd, dP_upd: metrices, 3D objects
|
|
|
|
|
|
Results of the prediction steps.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
log_likelihood_update: double or 1D array
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Update to the log_likelihood from this step
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
d_log_likelihood_update: (grad_params_no, time_series_no) matrix
|
|
|
|
|
|
Update to the gradient of log_likelihood, "multiple time series mode"
|
|
|
|
|
|
adds extra columns to the gradient.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
m_pred = p_m # from prediction step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
P_pred,S_pred,V_pred = p_P # from prediction step
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
H = p_meas_model_callable.Hk(k, m_pred, P_pred)
|
|
|
|
|
|
R = p_meas_model_callable.Rk(k)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
R_isr = p_meas_model_callable.R_isrk(k) # square root of the inverse of R matrix
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
time_series_no = p_m.shape[1] # number of time serieses
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
log_likelihood_update=None; dm_upd=None; dP_upd=None; d_log_likelihood_update=None
|
|
|
|
|
|
# Update step (only if there is data)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#if not np.any(np.isnan(measurement)): # TODO: if some dimensions are missing, do properly computations for other.
|
|
|
|
|
|
v = measurement-p_meas_model_callable.f_h(k, m_pred, H)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
svd_2_matr = np.vstack( ( np.dot( R_isr.T, np.dot(H, V_pred)) , np.diag( 1.0/np.sqrt(S_pred) ) ) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
(U,S,Vh) = sp.linalg.svd( svd_2_matr,full_matrices=False, compute_uv=True,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
overwrite_a=False,check_finite=True)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# P_upd = U_upd S_upd**2 U_upd.T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
U_upd = np.dot(V_pred, Vh.T)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
S_upd = (1.0/S)**2
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
P_upd = np.dot(U_upd * S_upd, U_upd.T) # update covariance
|
|
|
|
|
|
P_upd = (P_upd,S_upd,U_upd) # tuple to pass to the next step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# stil need to compute S and K for derivative computation
|
|
|
|
|
|
S = H.dot(P_pred).dot(H.T) + R
|
|
|
|
|
|
if measurement.shape[0]==1: # measurements are one dimensional
|
|
|
|
|
|
if (S < 0):
|
2015-10-08 15:30:34 +03:00
|
|
|
|
raise ValueError("Kalman Filter Update: S is negative step %i" % k )
|
|
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
K = P_pred.dot(H.T) / S
|
|
|
|
|
|
if calc_log_likelihood:
|
|
|
|
|
|
log_likelihood_update = -0.5 * ( np.log(2*np.pi) + np.log(S) +
|
2015-10-08 15:30:34 +03:00
|
|
|
|
v*v / S)
|
|
|
|
|
|
#log_likelihood_update = log_likelihood_update[0,0] # to make int
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if np.any(np.isnan(log_likelihood_update)): # some member in P_pred is None.
|
|
|
|
|
|
raise ValueError("Nan values in likelihood update!")
|
|
|
|
|
|
LL = None; islower = None
|
|
|
|
|
|
else:
|
2015-10-08 15:30:34 +03:00
|
|
|
|
LL,islower = linalg.cho_factor(S)
|
|
|
|
|
|
K = linalg.cho_solve((LL,islower), H.dot(P_pred.T)).T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if calc_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
log_likelihood_update = -0.5 * ( v.shape[0]*np.log(2*np.pi) +
|
2015-10-08 15:30:34 +03:00
|
|
|
|
2*np.sum( np.log(np.diag(LL)) ) +\
|
2016-03-15 17:01:05 +02:00
|
|
|
|
np.sum((linalg.cho_solve((LL,islower),v)) * v, axis = 0) ) # diagonal of v.T*S^{-1}*v
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# Old method of computing updated covariance (for testing) ->
|
|
|
|
|
|
#P_upd_tst = K.dot(S).dot(K.T)
|
|
|
|
|
|
#P_upd_tst = 0.5*(P_upd_tst + P_upd_tst.T)
|
|
|
|
|
|
#P_upd_tst = P_pred - P_upd_tst# this update matrix is symmetric
|
|
|
|
|
|
# Old method of computing updated covariance (for testing) <-
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dm_pred_all_params = p_dm # derivativas of the prediction phase
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_pred_all_params = p_dP
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
param_number = p_dP.shape[2]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dH_all_params = p_meas_model_callable.dHk(k)
|
|
|
|
|
|
dR_all_params = p_meas_model_callable.dRk(k)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm_upd = np.empty(dm_pred_all_params.shape)
|
|
|
|
|
|
dP_upd = np.empty(dP_pred_all_params.shape)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# firts dimension parameter_no, second - time series number
|
|
|
|
|
|
d_log_likelihood_update = np.empty((param_number,time_series_no))
|
|
|
|
|
|
for param in range(param_number):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dH = dH_all_params[:,:,param]
|
|
|
|
|
|
dR = dR_all_params[:,:,param]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm_pred = dm_pred_all_params[:,:,param]
|
|
|
|
|
|
dP_pred = dP_pred_all_params[:,:,param]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# Terms in the likelihood derivatives
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dv = - np.dot( dH, m_pred) - np.dot( H, dm_pred)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dS = np.dot(dH, np.dot( P_pred, H.T))
|
|
|
|
|
|
dS += dS.T
|
|
|
|
|
|
dS += np.dot(H, np.dot( dP_pred, H.T)) + dR
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# TODO: maybe symmetrize dS
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#dm and dP for the next stem
|
|
|
|
|
|
if LL is not None: # the state vector is not a scalar
|
|
|
|
|
|
tmp1 = linalg.cho_solve((LL,islower), H).T
|
|
|
|
|
|
tmp2 = linalg.cho_solve((LL,islower), dH).T
|
|
|
|
|
|
tmp3 = linalg.cho_solve((LL,islower), dS).T
|
|
|
|
|
|
else: # the state vector is a scalar
|
|
|
|
|
|
tmp1 = H.T / S
|
|
|
|
|
|
tmp2 = dH.T / S
|
|
|
|
|
|
tmp3 = dS.T / S
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dK = np.dot( dP_pred, tmp1) + np.dot( P_pred, tmp2) - \
|
|
|
|
|
|
np.dot( P_pred, np.dot( tmp1, tmp3 ) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# terms required for the next step, save this for each parameter
|
|
|
|
|
|
dm_upd[:,:,param] = dm_pred + np.dot(dK, v) + np.dot(K, dv)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
dP_upd[:,:,param] = -np.dot(dK, np.dot(S, K.T))
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_upd[:,:,param] += dP_upd[:,:,param].T
|
|
|
|
|
|
dP_upd[:,:,param] += dP_pred - np.dot(K , np.dot( dS, K.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dP_upd[:,:,param] = 0.5*(dP_upd[:,:,param] + dP_upd[:,:,param].T) #symmetrize
|
|
|
|
|
|
# computing the likelihood change for each parameter:
|
|
|
|
|
|
if LL is not None: # the state vector is not 1D
|
|
|
|
|
|
tmp5 = linalg.cho_solve((LL,islower), v)
|
|
|
|
|
|
else: # the state vector is a scalar
|
|
|
|
|
|
tmp5 = v / S
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
d_log_likelihood_update[param,:] = -(0.5*np.sum(np.diag(tmp3)) + \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
np.sum(tmp5*dv, axis=0) - 0.5 * np.sum(tmp5 * np.dot(dS, tmp5), axis=0) )
|
|
|
|
|
|
# Before
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#d_log_likelihood_update[param,0] = -(0.5*np.sum(np.diag(tmp3)) + \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#np.dot(tmp5.T, dv) - 0.5 * np.dot(tmp5.T ,np.dot(dS, tmp5)) )
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# Compute the actual updates for mean of the states. Variance update
|
|
|
|
|
|
# is computed earlier.
|
|
|
|
|
|
m_upd = m_pred + K.dot( v )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@staticmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def _rts_smoother_update_step(k, p_m , p_P, p_m_pred, p_P_pred, p_m_prev_step,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_P_prev_step, p_dynamic_callables):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Rauch–Tung–Striebel(RTS) update step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
-----------------------------
|
2015-04-07 18:20:11 +03:00
|
|
|
|
k: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Iteration No. Starts at 0. Total number of iterations equal to the
|
2015-04-07 18:20:11 +03:00
|
|
|
|
number of measurements.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_m: matrix of size (state_dim, time_series_no)
|
|
|
|
|
|
Filter mean on step k
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_P: matrix of size (state_dim,state_dim)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Filter Covariance on step k
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_m_pred: matrix of size (state_dim, time_series_no)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Means from the smoother prediction step.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_P_pred:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Covariance from the smoother prediction step.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_m_prev_step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Smoother mean from the previous step.
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_P_prev_step:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Smoother covariance from the previous step.
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_A: function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_a.
|
|
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
"""
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
A = p_dynamic_callables.Ak(k,p_m,p_P) # state transition matrix (or Jacobian)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
tmp = np.dot( A, p_P.T)
|
|
|
|
|
|
if A.shape[0] == 1: # 1D states
|
|
|
|
|
|
G = tmp.T / p_P_pred # P[:,:,k] is symmetric
|
|
|
|
|
|
else:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
try:
|
|
|
|
|
|
LL,islower = linalg.cho_factor(p_P_pred)
|
|
|
|
|
|
G = linalg.cho_solve((LL,islower),tmp).T
|
|
|
|
|
|
except:
|
|
|
|
|
|
# It happende that p_P_pred has several near zero eigenvalues
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# hence the Cholesky method does not work.
|
2015-07-14 16:44:21 +03:00
|
|
|
|
res = sp.linalg.lstsq(p_P_pred, tmp)
|
|
|
|
|
|
G = res[0].T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
m_upd = p_m + G.dot( p_m_prev_step-p_m_pred )
|
|
|
|
|
|
P_upd = p_P + G.dot( p_P_prev_step-p_P_pred).dot(G.T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
P_upd = 0.5*(P_upd + P_upd.T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return m_upd, P_upd, G
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def rts_smoother(cls,state_dim, p_dynamic_callables, filter_means,
|
2015-04-07 18:20:11 +03:00
|
|
|
|
filter_covars):
|
|
|
|
|
|
"""
|
|
|
|
|
|
This function implements Rauch–Tung–Striebel(RTS) smoother algorithm
|
|
|
|
|
|
based on the results of kalman_filter_raw.
|
|
|
|
|
|
These notations are the same:
|
|
|
|
|
|
x_{k} = A_{k} * x_{k-1} + q_{k-1}; q_{k-1} ~ N(0, Q_{k-1})
|
|
|
|
|
|
y_{k} = H_{k} * x_{k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Returns estimated smoother distributions x_{k} ~ N(m_{k}, P(k))
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_a: function (k, x_{k-1}, A_{k}). Dynamic function.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
x_{k-1} State from the previous step
|
|
|
|
|
|
A_{k} Jacobian matrices of f_a. In the linear case it is exactly A_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_A: function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_a.
|
|
|
|
|
|
k (iteration number), starts at 0
|
|
|
|
|
|
m: point where Jacobian is evaluated
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
p_f_Q: function (k). Returns noise matrix of dynamic model on iteration k.
|
|
|
|
|
|
k (iteration number). starts at 0
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
filter_means: (no_steps+1,state_dim) matrix or (no_steps+1,state_dim, time_series_no) 3D array
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Results of the Kalman Filter means estimation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
filter_covars: (no_steps+1, state_dim, state_dim) 3D array
|
|
|
|
|
|
Results of the Kalman Filter covariance estimation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
-------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
M: (no_steps+1, state_dim) matrix
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Smoothed estimates of the state means
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P: (no_steps+1, state_dim, state_dim) 3D array
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Smoothed estimates of the state covariances
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
no_steps = filter_covars.shape[0]-1# number of steps (minus initial covariance)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
M = np.empty(filter_means.shape) # smoothed means
|
|
|
|
|
|
P = np.empty(filter_covars.shape) # smoothed covars
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#G = np.empty( (no_steps,state_dim,state_dim) ) # G from the update step of the smoother
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
M[-1,:] = filter_means[-1,:]
|
|
|
|
|
|
P[-1,:,:] = filter_covars[-1,:,:]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
for k in range(no_steps-1,-1,-1):
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
m_pred, P_pred, tmp1, tmp2 = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_prediction_step(k, filter_means[k,:],
|
|
|
|
|
|
filter_covars[k,:,:], p_dynamic_callables,
|
|
|
|
|
|
calc_grad_log_likelihood=False)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_m = filter_means[k,:]
|
|
|
|
|
|
if len(p_m.shape)<2:
|
|
|
|
|
|
p_m.shape = (p_m.shape[0],1)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_m_prev_step = M[k+1,:]
|
|
|
|
|
|
if len(p_m_prev_step.shape)<2:
|
|
|
|
|
|
p_m_prev_step.shape = (p_m_prev_step.shape[0],1)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
m_upd, P_upd, G_tmp = cls._rts_smoother_update_step(k,
|
|
|
|
|
|
p_m ,filter_covars[k,:,:],
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_pred, P_pred, p_m_prev_step ,P[k+1,:,:], p_dynamic_callables)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
M[k,:] = m_upd#np.squeeze(m_upd)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P[k,:,:] = P_upd
|
2015-08-10 19:40:39 +03:00
|
|
|
|
#G[k,:,:] = G_upd.T # store transposed G.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Return values
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return (M, P) #, G)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _EM_gradient(A,Q,H,R,m_init,P_init,measurements, M, P, G, dA, dQ, dH, dR, dm_init, dP_init):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Gradient computation with the EM algorithm.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
-----------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
M: Means from the smoother
|
|
|
|
|
|
P: Variances from the smoother
|
|
|
|
|
|
G: Gains? from the smoother
|
|
|
|
|
|
"""
|
|
|
|
|
|
import pdb; pdb.set_trace();
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
param_number = dA.shape[-1]
|
|
|
|
|
|
d_log_likelihood_update = np.empty((param_number,1))
|
|
|
|
|
|
|
|
|
|
|
|
sample_no = measurements.shape[0]
|
|
|
|
|
|
P_1 = P[1:,:,:] # remove 0-th step
|
|
|
|
|
|
P_2 = P[0:-1,:,:] # remove 0-th step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
M_1 = M[1:,:] # remove 0-th step
|
|
|
|
|
|
M_2 = M[0:-1,:] # remove the last step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
Sigma = np.mean(P_1,axis=0) + np.dot(M_1.T, M_1) / sample_no #
|
|
|
|
|
|
Phi = np.mean(P_2,axis=0) + np.dot(M_2.T, M_2) / sample_no #
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
B = np.dot( measurements.T, M_1 )/ sample_no
|
|
|
|
|
|
C = (sp.einsum( 'ijk,ikl', P_1, G) + np.dot(M_1.T, M_2)) / sample_no #
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# C1 = np.zeros( (P_1.shape[1],P_1.shape[1]) )
|
|
|
|
|
|
# for k in range(P_1.shape[0]):
|
|
|
|
|
|
# C1 += np.dot(P_1[k,:,:],G[k,:,:]) + sp.outer( M_1[k,:], M_2[k,:] )
|
|
|
|
|
|
# C1 = C1 / sample_no
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
D = np.dot( measurements.T, measurements ) / sample_no
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
try:
|
|
|
|
|
|
P_init_inv = sp.linalg.inv(P_init)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if np.max( np.abs(P_init_inv)) > 10e13:
|
|
|
|
|
|
compute_P_init_terms = False
|
|
|
|
|
|
else:
|
|
|
|
|
|
compute_P_init_terms = True
|
|
|
|
|
|
except np.linalg.LinAlgError:
|
|
|
|
|
|
compute_P_init_terms = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
try:
|
|
|
|
|
|
Q_inv = sp.linalg.inv(Q)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if np.max( np.abs(Q_inv)) > 10e13:
|
|
|
|
|
|
compute_Q_terms = False
|
|
|
|
|
|
else:
|
|
|
|
|
|
compute_Q_terms = True
|
|
|
|
|
|
except np.linalg.LinAlgError:
|
|
|
|
|
|
compute_Q_terms = False
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
R_inv = sp.linalg.inv(R)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if np.max( np.abs(R_inv)) > 10e13:
|
|
|
|
|
|
compute_R_terms = False
|
|
|
|
|
|
else:
|
|
|
|
|
|
compute_R_terms = True
|
|
|
|
|
|
except np.linalg.LinAlgError:
|
|
|
|
|
|
compute_R_terms = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
d_log_likelihood_update = np.zeros((param_number,1))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
for j in range(param_number):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if compute_P_init_terms:
|
|
|
|
|
|
d_log_likelihood_update[j,:] -= 0.5 * np.sum(P_init_inv* dP_init[:,:,j].T ) #p #m
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
M0_smoothed = M[0]; M0_smoothed.shape = (M0_smoothed.shape[0],1)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
tmp1 = np.dot( dP_init[:,:,j], np.dot( P_init_inv, (P[0,:,:] + sp.outer( (M0_smoothed - m_init), (M0_smoothed - m_init) )) ) ) #p #m
|
2015-08-10 19:40:39 +03:00
|
|
|
|
d_log_likelihood_update[j,:] += 0.5 * np.sum(P_init_inv* tmp1.T )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
tmp2 = sp.outer( dm_init[:,j], M0_smoothed )
|
|
|
|
|
|
tmp2 += tmp2.T
|
|
|
|
|
|
d_log_likelihood_update[j,:] += 0.5 * np.sum(P_init_inv* tmp2.T )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if compute_Q_terms:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
d_log_likelihood_update[j,:] -= sample_no/2.0 * np.sum(Q_inv* dQ[:,:,j].T ) #m
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
tmp1 = np.dot(C,A.T); tmp1 += tmp1.T; tmp1 = Sigma - tmp1 + np.dot(A, np.dot(Phi,A.T)) #m
|
2015-08-10 19:40:39 +03:00
|
|
|
|
tmp1 = np.dot( dQ[:,:,j], np.dot( Q_inv, tmp1) )
|
|
|
|
|
|
d_log_likelihood_update[j,:] += sample_no/2.0 * np.sum(Q_inv * tmp1.T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
tmp2 = np.dot( dA[:,:,j], C.T); tmp2 += tmp2.T;
|
2015-08-10 19:40:39 +03:00
|
|
|
|
tmp3 = np.dot(dA[:,:,j], np.dot(Phi,A.T)); tmp3 += tmp3.T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
d_log_likelihood_update[j,:] -= sample_no/2.0 * np.sum(Q_inv.T * (tmp3 - tmp2) )
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if compute_R_terms:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
d_log_likelihood_update[j,:] -= sample_no/2.0 * np.sum(R_inv* dR[:,:,j].T )
|
|
|
|
|
|
|
|
|
|
|
|
tmp1 = np.dot(B,H.T); tmp1 += tmp1.T; tmp1 = D - tmp1 + np.dot(H, np.dot(Sigma,H.T))
|
2015-08-10 19:40:39 +03:00
|
|
|
|
tmp1 = np.dot( dR[:,:,j], np.dot( R_inv, tmp1) )
|
|
|
|
|
|
d_log_likelihood_update[j,:] += sample_no/2.0 * np.sum(R_inv * tmp1.T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
tmp2 = np.dot( dH[:,:,j], B.T); tmp2 += tmp2.T;
|
2015-08-10 19:40:39 +03:00
|
|
|
|
tmp3 = np.dot(dH[:,:,j], np.dot(Sigma,H.T)); tmp3 += tmp3.T
|
|
|
|
|
|
d_log_likelihood_update[j,:] -= sample_no/2.0 * np.sum(R_inv.T * (tmp3 - tmp2) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return d_log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@staticmethod
|
2015-07-14 16:44:21 +03:00
|
|
|
|
def _check_SS_matrix(p_M, state_dim, measurement_dim, which='A'):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Veryfy that on exit the matrix has appropriate shape for the KF algorithm.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
p_M: matrix
|
2015-07-14 16:44:21 +03:00
|
|
|
|
As it is given for the user
|
|
|
|
|
|
state_dim: int
|
|
|
|
|
|
State dimensioanlity
|
|
|
|
|
|
measurement_dim: int
|
|
|
|
|
|
Measurement dimensionality
|
|
|
|
|
|
which: string
|
|
|
|
|
|
One of: 'A', 'Q', 'H', 'R'
|
|
|
|
|
|
Output:
|
|
|
|
|
|
---------------
|
|
|
|
|
|
p_M: matrix of the right shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
old_M_shape: tuple
|
|
|
|
|
|
Old Shape
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
old_M_shape = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
if len(p_M.shape) < 3: # new shape is 3 dimensional
|
2015-07-14 16:44:21 +03:00
|
|
|
|
old_M_shape = p_M.shape # save shape to restore it on exit
|
|
|
|
|
|
if len(p_M.shape) == 2: # matrix
|
|
|
|
|
|
p_M.shape = (p_M.shape[0],p_M.shape[1],1)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
elif len(p_M.shape) == 1: # scalar but in array already
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if (p_M.shape[0] != 1):
|
|
|
|
|
|
raise ValueError("Matrix %s is an 1D array, while it must be a matrix or scalar", which)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_M.shape = (1,1,1)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if (which == 'A') or (which == 'Q'):
|
|
|
|
|
|
if (p_M.shape[0] != state_dim) or (p_M.shape[1] != state_dim):
|
|
|
|
|
|
raise ValueError("%s must be a square matrix of size (%i,%i)" % (which, state_dim, state_dim))
|
|
|
|
|
|
if (which == 'H'):
|
|
|
|
|
|
if (p_M.shape[0] != measurement_dim) or (p_M.shape[1] != state_dim):
|
|
|
|
|
|
raise ValueError("H must be of shape (measurement_dim, state_dim) (%i,%i)" % (measurement_dim, state_dim))
|
|
|
|
|
|
if (which == 'R'):
|
|
|
|
|
|
if (p_M.shape[0] != measurement_dim) or (p_M.shape[1] != measurement_dim):
|
|
|
|
|
|
raise ValueError("R must be of shape (measurement_dim, measurement_dim) (%i,%i)" % (measurement_dim, measurement_dim))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
return (p_M,old_M_shape)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2015-04-07 18:20:11 +03:00
|
|
|
|
def _check_grad_state_matrices(dM, state_dim, grad_params_no, which = 'dA'):
|
|
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Function checks (mostly check dimensions) matrices for marginal likelihood
|
|
|
|
|
|
gradient parameters calculation. It check dA, dQ matrices.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Input:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
-------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dM: None, scaler or 3D matrix
|
2015-07-14 16:44:21 +03:00
|
|
|
|
It is supposed to be (state_dim,state_dim,grad_params_no) matrix.
|
|
|
|
|
|
If None then zero matrix is assumed. If scalar then the function
|
|
|
|
|
|
checks consistency with "state_dim" and "grad_params_no".
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
state_dim: int
|
|
|
|
|
|
State dimensionality
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no: int
|
|
|
|
|
|
How many parrameters of likelihood gradient in total.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
which: string
|
|
|
|
|
|
'dA' or 'dQ'
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function of (k) which returns the parameters matrix.
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if dM is None:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dM=np.zeros((state_dim,state_dim,grad_params_no))
|
2015-04-07 18:20:11 +03:00
|
|
|
|
elif isinstance(dM, np.ndarray):
|
|
|
|
|
|
if state_dim == 1:
|
|
|
|
|
|
if len(dM.shape) < 3:
|
|
|
|
|
|
dM.shape = (1,1,1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if len(dM.shape) < 3:
|
|
|
|
|
|
dM.shape = (state_dim,state_dim,1)
|
|
|
|
|
|
elif isinstance(dM, np.int):
|
|
|
|
|
|
if state_dim > 1:
|
|
|
|
|
|
raise ValueError("When computing likelihood gradient wrong %s dimension." % which)
|
|
|
|
|
|
else:
|
|
|
|
|
|
dM = np.ones((1,1,1)) * dM
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# if not isinstance(dM, types.FunctionType):
|
|
|
|
|
|
# f_dM = lambda k: dM
|
|
|
|
|
|
# else:
|
|
|
|
|
|
# f_dM = dM
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return dM
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2015-04-07 18:20:11 +03:00
|
|
|
|
def _check_grad_measurement_matrices(dM, state_dim, grad_params_no, measurement_dim, which = 'dH'):
|
|
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Function checks (mostly check dimensions) matrices for marginal likelihood
|
|
|
|
|
|
gradient parameters calculation. It check dH, dR matrices.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Input:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
-------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dM: None, scaler or 3D matrix
|
|
|
|
|
|
It is supposed to be
|
2015-07-14 16:44:21 +03:00
|
|
|
|
(measurement_dim ,state_dim,grad_params_no) for "dH" matrix.
|
|
|
|
|
|
(measurement_dim,measurement_dim,grad_params_no) for "dR"
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
If None then zero matrix is assumed. If scalar then the function
|
|
|
|
|
|
checks consistency with "state_dim" and "grad_params_no".
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
state_dim: int
|
|
|
|
|
|
State dimensionality
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no: int
|
|
|
|
|
|
How many parrameters of likelihood gradient in total.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
measurement_dim: int
|
|
|
|
|
|
Dimensionality of measurements.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
which: string
|
|
|
|
|
|
'dH' or 'dR'
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
function of (k) which returns the parameters matrix.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if dM is None:
|
|
|
|
|
|
if which == 'dH':
|
|
|
|
|
|
dM=np.zeros((measurement_dim ,state_dim,grad_params_no))
|
|
|
|
|
|
elif which == 'dR':
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dM=np.zeros((measurement_dim,measurement_dim,grad_params_no))
|
2015-04-07 18:20:11 +03:00
|
|
|
|
elif isinstance(dM, np.ndarray):
|
|
|
|
|
|
if state_dim == 1:
|
|
|
|
|
|
if len(dM.shape) < 3:
|
|
|
|
|
|
dM.shape = (1,1,1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if len(dM.shape) < 3:
|
|
|
|
|
|
if which == 'dH':
|
|
|
|
|
|
dM.shape = (measurement_dim,state_dim,1)
|
|
|
|
|
|
elif which == 'dR':
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dM.shape = (measurement_dim,measurement_dim,1)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
elif isinstance(dM, np.int):
|
|
|
|
|
|
if state_dim > 1:
|
|
|
|
|
|
raise ValueError("When computing likelihood gradient wrong dH dimension.")
|
|
|
|
|
|
else:
|
|
|
|
|
|
dM = np.ones((1,1,1)) * dM
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# if not isinstance(dM, types.FunctionType):
|
|
|
|
|
|
# f_dM = lambda k: dM
|
|
|
|
|
|
# else:
|
|
|
|
|
|
# f_dM = dM
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return dM
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
class Struct(object):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ContDescrStateSpace(DescreteStateSpace):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Class for continuous-discrete Kalman filter. State equation is
|
|
|
|
|
|
continuous while measurement equation is discrete.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
d x(t)/ dt = F x(t) + L q; where q~ N(0, Qc)
|
|
|
|
|
|
y_{t_k} = H_{k} x_{t_k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class AQcompute_once(Q_handling_Class):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Class for calculating matrices A, Q, dA, dQ of the discrete Kalman Filter
|
|
|
|
|
|
from the matrices F, L, Qc, P_ing, dF, dQc, dP_inf of the continuos state
|
|
|
|
|
|
equation. dt - time steps.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
It has the same interface as AQcompute_batch.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
It computes matrices for only one time step. This object is used when
|
|
|
|
|
|
there are many different time steps and storing matrices for each of them
|
|
|
|
|
|
would take too much memory.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
def __init__(self, F,L,Qc,dt,compute_derivatives=False, grad_params_no=None, P_inf=None, dP_inf=None, dF = None, dQc=None):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Constructor. All necessary parameters are passed here and stored
|
|
|
|
|
|
in the opject.
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
-------------------
|
|
|
|
|
|
F, L, Qc, P_inf : matrices
|
|
|
|
|
|
Parameters of corresponding continuous state model
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dt: array
|
2015-07-14 16:44:21 +03:00
|
|
|
|
All time steps
|
|
|
|
|
|
compute_derivatives: bool
|
|
|
|
|
|
Whether to calculate derivatives
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dP_inf, dF, dQc: 3D array
|
|
|
|
|
|
Derivatives if they are required
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
-------------------
|
|
|
|
|
|
Nothing
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Copies are done because this object is used later in smoother
|
|
|
|
|
|
# and these parameters must not change.
|
|
|
|
|
|
self.F = F.copy()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.L = L.copy()
|
2015-07-14 16:44:21 +03:00
|
|
|
|
self.Qc = Qc.copy()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
self.dt = dt # copy is not taken because dt is internal parameter
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
# Parameters are used to calculate derivatives but derivatives
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# are not used in the smoother. Therefore copies are not taken.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.P_inf = P_inf
|
2015-04-07 18:20:11 +03:00
|
|
|
|
self.dP_inf = dP_inf
|
|
|
|
|
|
self.dF = dF
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.dQc = dQc
|
|
|
|
|
|
|
|
|
|
|
|
self.compute_derivatives = compute_derivatives
|
|
|
|
|
|
self.grad_params_no = grad_params_no
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
self.last_k = 0
|
|
|
|
|
|
self.last_k_computed = False
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.v_Ak = None
|
|
|
|
|
|
self.v_Qk = None
|
|
|
|
|
|
self.v_dAk = None
|
|
|
|
|
|
self.v_dQk = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.square_root_computed = False
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# !!!Print statistics! Which object is created
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def f_a(self, k,m,A):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Dynamic model
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return np.dot(A, m) # default dynamic model
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
def _recompute_for_new_k(self,k):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Computes the necessary matrices for an index k and store the results.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
----------------------
|
|
|
|
|
|
k: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Index in the time differences array dt where to compute matrices
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
----------------------
|
|
|
|
|
|
Ak,Qk, dAk, dQk: matrices and/or 3D arrays
|
|
|
|
|
|
A, Q, dA dQ on step k
|
|
|
|
|
|
"""
|
|
|
|
|
|
if (self.last_k != k) or (self.last_k_computed == False):
|
2015-09-04 19:44:19 +03:00
|
|
|
|
v_Ak,v_Qk, tmp, v_dAk, v_dQk = ContDescrStateSpace.lti_sde_to_descrete(self.F,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
self.L,self.Qc,self.dt[k],self.compute_derivatives,
|
2015-04-07 18:20:11 +03:00
|
|
|
|
grad_params_no=self.grad_params_no, P_inf=self.P_inf, dP_inf=self.dP_inf, dF=self.dF, dQc=self.dQc)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
self.last_k = k
|
|
|
|
|
|
self.last_k_computed = True
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.v_Ak = v_Ak
|
|
|
|
|
|
self.v_Qk = v_Qk
|
|
|
|
|
|
self.v_dAk = v_dAk
|
|
|
|
|
|
self.v_dQk = v_dQk
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.Q_square_root_computed = False
|
2015-07-14 16:44:21 +03:00
|
|
|
|
else:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
v_Ak = self.v_Ak
|
|
|
|
|
|
v_Qk = self.v_Qk
|
|
|
|
|
|
v_dAk = self.v_dAk
|
|
|
|
|
|
v_dQk = self.v_dQk
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# !!!Print statistics! Print sizes of matrices
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return v_Ak,v_Qk, v_dAk, v_dQk
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
def reset(self, compute_derivatives):
|
|
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
For reusing this object e.g. in smoother computation. Actually,
|
|
|
|
|
|
this object can not be reused because it computes the matrices on
|
2016-03-15 17:01:05 +02:00
|
|
|
|
every iteration. But this method is written for keeping the same
|
2015-07-14 16:44:21 +03:00
|
|
|
|
interface with the class AQcompute_batch.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
self.last_k = 0
|
|
|
|
|
|
self.last_k_computed = False
|
|
|
|
|
|
self.compute_derivatives = compute_derivatives
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.Q_square_root_computed = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return self
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Ak(self,k,m,P):
|
|
|
|
|
|
v_Ak,v_Qk, v_dAk, v_dQk = self._recompute_for_new_k(k)
|
|
|
|
|
|
return v_Ak
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Qk(self,k):
|
|
|
|
|
|
v_Ak,v_Qk, v_dAk, v_dQk = self._recompute_for_new_k(k)
|
|
|
|
|
|
return v_Qk
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def dAk(self, k):
|
|
|
|
|
|
v_Ak,v_Qk, v_dAk, v_dQk = self._recompute_for_new_k(k)
|
|
|
|
|
|
return v_dAk
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def dQk(self, k):
|
|
|
|
|
|
v_Ak,v_Qk, v_dAk, v_dQk = self._recompute_for_new_k(k)
|
|
|
|
|
|
return v_dQk
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Q_srk(self,k):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Square root of the noise matrix Q
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if ((self.last_k == k) and (self.last_k_computed == True)):
|
|
|
|
|
|
if not self.Q_square_root_computed:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
(U, S, Vh) = sp.linalg.svd( self.v_Qk, full_matrices=False, compute_uv=True, overwrite_a=False, check_finite=False)
|
2015-08-10 19:40:39 +03:00
|
|
|
|
square_root = U * np.sqrt(S)
|
|
|
|
|
|
self.square_root_computed = True
|
|
|
|
|
|
self.Q_square_root = square_root
|
|
|
|
|
|
else:
|
|
|
|
|
|
square_root = self.Q_square_root
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError("Square root of Q can not be computed")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return square_root
|
|
|
|
|
|
|
|
|
|
|
|
def return_last(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Function returns last computed matrices.
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.last_k_computed:
|
|
|
|
|
|
raise ValueError("Matrices are not computed.")
|
|
|
|
|
|
else:
|
|
|
|
|
|
k = self.last_k
|
2015-09-04 19:44:19 +03:00
|
|
|
|
A = self.v_Ak
|
|
|
|
|
|
Q = self.v_Qk
|
|
|
|
|
|
dA = self.v_dAk
|
|
|
|
|
|
dQ = self.v_dQk
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
return k, A, Q, dA, dQ
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class AQcompute_batch_Python(Q_handling_Class):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Class for calculating matrices A, Q, dA, dQ of the discrete Kalman Filter
|
|
|
|
|
|
from the matrices F, L, Qc, P_ing, dF, dQc, dP_inf of the continuos state
|
2016-03-15 17:01:05 +02:00
|
|
|
|
equation. dt - time steps.
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
It has the same interface as AQcompute_once.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
It computes matrices for all time steps. This object is used when
|
2016-03-15 17:01:05 +02:00
|
|
|
|
there are not so many (controlled by internal variable)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
different time steps and storing all the matrices do not take too much memory.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Since all the matrices are computed all together, this object can be used
|
2016-03-15 17:01:05 +02:00
|
|
|
|
in smoother without repeating the computations.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
|
|
|
|
|
def __init__(self, F,L,Qc,dt,compute_derivatives=False, grad_params_no=None, P_inf=None, dP_inf=None, dF = None, dQc=None):
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Constructor. All necessary parameters are passed here and stored
|
|
|
|
|
|
in the opject.
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
-------------------
|
|
|
|
|
|
F, L, Qc, P_inf : matrices
|
|
|
|
|
|
Parameters of corresponding continuous state model
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dt: array
|
2015-07-14 16:44:21 +03:00
|
|
|
|
All time steps
|
|
|
|
|
|
compute_derivatives: bool
|
|
|
|
|
|
Whether to calculate derivatives
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dP_inf, dF, dQc: 3D array
|
|
|
|
|
|
Derivatives if they are required
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
-------------------
|
|
|
|
|
|
Nothing
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
|
|
|
|
|
As, Qs, reconstruct_indices, dAs, dQs = ContDescrStateSpace.lti_sde_to_descrete(F,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
L,Qc,dt,compute_derivatives,
|
2015-04-07 18:20:11 +03:00
|
|
|
|
grad_params_no=grad_params_no, P_inf=P_inf, dP_inf=dP_inf, dF=dF, dQc=dQc)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
self.As = As
|
|
|
|
|
|
self.Qs = Qs
|
|
|
|
|
|
self.dAs = dAs
|
|
|
|
|
|
self.dQs = dQs
|
|
|
|
|
|
self.reconstruct_indices = reconstruct_indices
|
2015-07-14 16:44:21 +03:00
|
|
|
|
self.total_size_of_data = self.As.nbytes + self.Qs.nbytes +\
|
|
|
|
|
|
(self.dAs.nbytes if (self.dAs is not None) else 0) +\
|
|
|
|
|
|
(self.dQs.nbytes if (self.dQs is not None) else 0) +\
|
|
|
|
|
|
(self.reconstruct_indices.nbytes if (self.reconstruct_indices is not None) else 0)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.Q_svd_dict = {}
|
2015-09-04 19:44:19 +03:00
|
|
|
|
self.last_k = None
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# !!!Print statistics! Which object is created
|
|
|
|
|
|
# !!!Print statistics! Print sizes of matrices
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def f_a(self, k,m,A):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Dynamic model
|
2016-03-15 17:01:05 +02:00
|
|
|
|
"""
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return np.dot(A, m) # default dynamic model
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def reset(self, compute_derivatives=False):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
For reusing this object e.g. in smoother computation. It makes sence
|
|
|
|
|
|
because necessary matrices have been already computed for all
|
|
|
|
|
|
time steps.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
|
|
|
|
|
return self
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Ak(self,k,m,P):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.last_k = k
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return self.As[:,:, self.reconstruct_indices[k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Qk(self,k):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.last_k = k
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return self.Qs[:,:, self.reconstruct_indices[k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def dAk(self,k):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.last_k = k
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return self.dAs[:,:, :, self.reconstruct_indices[k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def dQk(self,k):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
self.last_k = k
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return self.dQs[:,:, :, self.reconstruct_indices[k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
def Q_srk(self,k):
|
2015-08-10 19:40:39 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Square root of the noise matrix Q
|
|
|
|
|
|
"""
|
|
|
|
|
|
matrix_index = self.reconstruct_indices[k]
|
|
|
|
|
|
if matrix_index in self.Q_svd_dict:
|
|
|
|
|
|
square_root = self.Q_svd_dict[matrix_index]
|
|
|
|
|
|
else:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
(U, S, Vh) = sp.linalg.svd( self.Qs[:,:, matrix_index],
|
|
|
|
|
|
full_matrices=False, compute_uv=True,
|
2015-08-10 19:40:39 +03:00
|
|
|
|
overwrite_a=False, check_finite=False)
|
|
|
|
|
|
square_root = U * np.sqrt(S)
|
|
|
|
|
|
self.Q_svd_dict[matrix_index] = square_root
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return square_root
|
|
|
|
|
|
|
|
|
|
|
|
def return_last(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Function returns last available matrices.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if (self.last_k is None):
|
|
|
|
|
|
raise ValueError("Matrices are not computed.")
|
|
|
|
|
|
else:
|
|
|
|
|
|
ind = self.reconstruct_indices[self.last_k]
|
|
|
|
|
|
A = self.As[:,:, ind]
|
|
|
|
|
|
Q = self.Qs[:,:, ind]
|
|
|
|
|
|
dA = self.dAs[:,:, :, ind]
|
|
|
|
|
|
dQ = self.dQs[:,:, :, ind]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
return self.last_k, A, Q, dA, dQ
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@classmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def cont_discr_kalman_filter(cls, F, L, Qc, p_H, p_R, P_inf, X, Y, index = None,
|
|
|
|
|
|
m_init=None, P_init=None,
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_kalman_filter_type='regular',
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_log_likelihood=False,
|
|
|
|
|
|
calc_grad_log_likelihood=False,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
grad_params_no=0, grad_calc_params=None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
This function implements the continuous-discrete Kalman Filter algorithm
|
|
|
|
|
|
These notations for the State-Space model are assumed:
|
|
|
|
|
|
d/dt x(t) = F * x(t) + L * w(t); w(t) ~ N(0, Qc)
|
|
|
|
|
|
y_{k} = H_{k} * x_{k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Returns estimated filter distributions x_{k} ~ N(m_{k}, P(k))
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Current Features:
|
|
|
|
|
|
----------------------------------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
1) The function generaly do not modify the passed parameters. If
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it happens then it is an error. There are several exeprions: scalars
|
2016-03-15 17:01:05 +02:00
|
|
|
|
can be modified into a matrix, in some rare cases shapes of
|
2015-07-14 16:44:21 +03:00
|
|
|
|
the derivatives matrices may be changed, it is ignored for now.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
2) Copies of F,L,Qc are created in memory because they may be used later
|
|
|
|
|
|
in smoother. References to copies are kept in "AQcomp" object
|
|
|
|
|
|
return parameter.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
3) Function support "multiple time series mode" which means that exactly
|
|
|
|
|
|
the same State-Space model is used to filter several sets of measurements.
|
|
|
|
|
|
In this case third dimension of Y should include these state-space measurements
|
|
|
|
|
|
Log_likelihood and Grad_log_likelihood have the corresponding dimensions then.
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
4) Calculation of Grad_log_likelihood is not supported if matrices
|
2015-07-14 16:44:21 +03:00
|
|
|
|
H, or R changes overf time (with index k). (later may be changed)
|
|
|
|
|
|
|
|
|
|
|
|
5) Measurement may include missing values. In this case update step is
|
|
|
|
|
|
not done for this measurement. (later may be changed)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Input:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
-----------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
F: (state_dim, state_dim) matrix
|
|
|
|
|
|
F in the model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
L: (state_dim, noise_dim) matrix
|
|
|
|
|
|
L in the model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Qc: (noise_dim, noise_dim) matrix
|
|
|
|
|
|
Q_c in the model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_H: scalar, matrix (measurement_dim, state_dim) , 3D array
|
|
|
|
|
|
H_{k} in the model. If matrix then H_{k} = H - constant.
|
|
|
|
|
|
If it is 3D array then H_{k} = p_Q[:,:, index[2,k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_R: scalar, square symmetric matrix, 3D array
|
|
|
|
|
|
R_{k} in the model. If matrix then R_{k} = R - constant.
|
|
|
|
|
|
If it is 3D array then R_{k} = p_R[:,:, index[3,k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_inf: (state_dim, state_dim) matrix
|
|
|
|
|
|
State varince matrix on infinity.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
X: 1D array
|
|
|
|
|
|
Time points of measurements. Needed for converting continuos
|
|
|
|
|
|
problem to the discrete one.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Y: matrix or vector or 3D array
|
|
|
|
|
|
Data. If Y is matrix then samples are along 0-th dimension and
|
2016-03-15 17:01:05 +02:00
|
|
|
|
features along the 1-st. If 3D array then third dimension
|
2015-07-14 16:44:21 +03:00
|
|
|
|
correspond to "multiple time series mode".
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
index: vector
|
|
|
|
|
|
Which indices (on 3-rd dimension) from arrays p_H, p_R to use
|
2016-03-15 17:01:05 +02:00
|
|
|
|
on every time step. If this parameter is None then it is assumed
|
2015-07-14 16:44:21 +03:00
|
|
|
|
that p_H, p_R do not change over time and indices are not needed.
|
|
|
|
|
|
index[0,:] - correspond to H, index[1,:] - correspond to R
|
|
|
|
|
|
If index.shape[0] == 1, it is assumed that indides for all matrices
|
|
|
|
|
|
are the same.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
m_init: vector or matrix
|
|
|
|
|
|
Initial distribution mean. If None it is assumed to be zero.
|
|
|
|
|
|
For "multiple time series mode" it is matrix, second dimension of
|
|
|
|
|
|
which correspond to different time series. In regular case ("one
|
|
|
|
|
|
time series mode") it is a vector.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_init: square symmetric matrix or scalar
|
|
|
|
|
|
Initial covariance of the states. If the parameter is scalar
|
2016-03-15 17:01:05 +02:00
|
|
|
|
then it is assumed that initial covariance matrix is unit matrix
|
2015-07-14 16:44:21 +03:00
|
|
|
|
multiplied by this scalar. If None the unit matrix is used instead.
|
|
|
|
|
|
"multiple time series mode" does not affect it, since it does not
|
|
|
|
|
|
affect anything related to state variaces.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_kalman_filter_type: string, one of ('regular', 'svd')
|
|
|
|
|
|
Which Kalman Filter is used. Regular or SVD. SVD is more numerically
|
|
|
|
|
|
stable, in particular, Covariace matrices are guarantied to be
|
|
|
|
|
|
positive semi-definite. However, 'svd' works slower, especially for
|
|
|
|
|
|
small data due to SVD call overhead.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then "grad_calc_params" parameter must
|
2015-07-14 16:44:21 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no: int
|
2016-03-15 17:01:05 +02:00
|
|
|
|
If previous parameter is true, then this parameters gives the
|
|
|
|
|
|
total number of parameters in the gradient.
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_calc_params: dictionary
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Dictionary with derivatives of model matrices with respect
|
2015-07-14 16:44:21 +03:00
|
|
|
|
to parameters "dF", "dL", "dQc", "dH", "dR", "dm_init", "dP_init".
|
|
|
|
|
|
They can be None, in this case zero matrices (no dependence on parameters)
|
|
|
|
|
|
is assumed. If there is only one parameter then third dimension is
|
|
|
|
|
|
automatically added.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
M: (no_steps+1,state_dim) matrix or (no_steps+1,state_dim, time_series_no) 3D array
|
|
|
|
|
|
Filter estimates of the state means. In the extra step the initial
|
|
|
|
|
|
value is included. In the "multiple time series mode" third dimension
|
|
|
|
|
|
correspond to different timeseries.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P: (no_steps+1, state_dim, state_dim) 3D array
|
|
|
|
|
|
Filter estimates of the state covariances. In the extra step the initial
|
|
|
|
|
|
value is included.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
log_likelihood: double or (1, time_series_no) 3D array.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
If the parameter calc_log_likelihood was set to true, return
|
|
|
|
|
|
logarithm of marginal likelihood of the state-space model. If
|
|
|
|
|
|
the parameter was false, return None. In the "multiple time series mode" it is a vector
|
|
|
|
|
|
providing log_likelihood for each time series.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_log_likelihood: column vector or (grad_params_no, time_series_no) matrix
|
|
|
|
|
|
If calc_grad_log_likelihood is true, return gradient of log likelihood
|
2016-03-15 17:01:05 +02:00
|
|
|
|
with respect to parameters. It returns it column wise, so in
|
|
|
|
|
|
"multiple time series mode" gradients for each time series is in the
|
2015-07-14 16:44:21 +03:00
|
|
|
|
corresponding column.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
AQcomp: object
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Contains some pre-computed values for converting continuos model into
|
|
|
|
|
|
discrete one. It can be used later in the smoothing pahse.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_H = np.atleast_1d(p_H)
|
|
|
|
|
|
p_R = np.atleast_1d(p_R)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
X.shape, old_X_shape = cls._reshape_input_data(X.shape, 2) # represent as column
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if (X.shape[1] != 1):
|
|
|
|
|
|
raise ValueError("Only one dimensional X data is supported.")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Y.shape, old_Y_shape = cls._reshape_input_data(Y.shape) # represent as column
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
state_dim = F.shape[0]
|
|
|
|
|
|
measurement_dim = Y.shape[1]
|
2015-10-08 15:30:34 +03:00
|
|
|
|
time_series_no = Y.shape[2] # multiple time series mode
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if ((len(p_H.shape) == 3) and (len(p_H.shape[2]) != 1)) or\
|
|
|
|
|
|
((len(p_R.shape) == 3) and (len(p_R.shape[2]) != 1)):
|
|
|
|
|
|
model_matrices_chage_with_time = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
model_matrices_chage_with_time = False
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Check index
|
|
|
|
|
|
old_index_shape = None
|
|
|
|
|
|
if index is None:
|
|
|
|
|
|
if (len(p_H.shape) == 3) or (len(p_R.shape) == 3):
|
|
|
|
|
|
raise ValueError("Parameter index can not be None for time varying matrices (third dimension is present)")
|
|
|
|
|
|
else: # matrices do not change in time, so form dummy zero indices.
|
|
|
|
|
|
index = np.zeros((1,Y.shape[0]))
|
|
|
|
|
|
else:
|
|
|
|
|
|
if len(index.shape) == 1:
|
|
|
|
|
|
index.shape = (1,index.shape[0])
|
|
|
|
|
|
old_index_shape = (index.shape[0],)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if (index.shape[1] != Y.shape[0]):
|
2015-07-14 16:44:21 +03:00
|
|
|
|
raise ValueError("Number of measurements must be equal the number of H_{k}, R_{k}")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if (index.shape[0] == 1):
|
|
|
|
|
|
H_time_var_index = 0; R_time_var_index = 0
|
2015-07-14 16:44:21 +03:00
|
|
|
|
elif (index.shape[0] == 4):
|
|
|
|
|
|
H_time_var_index = 0; R_time_var_index = 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError("First Dimension of index must be either 1 or 2.")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
(p_H, old_H_shape) = cls._check_SS_matrix(p_H, state_dim, measurement_dim, which='H')
|
|
|
|
|
|
(p_R, old_R_shape) = cls._check_SS_matrix(p_R, state_dim, measurement_dim, which='R')
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if m_init is None:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_init = np.zeros((state_dim, time_series_no))
|
2015-07-14 16:44:21 +03:00
|
|
|
|
else:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_init = np.atleast_2d(m_init).T
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if P_init is None:
|
|
|
|
|
|
P_init = P_inf.copy()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if p_kalman_filter_type not in ('regular', 'svd'):
|
|
|
|
|
|
raise ValueError("Kalman filer type neither 'regular nor 'svd'.")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# Functions to pass to the kalman_filter algorithm:
|
|
|
|
|
|
# Parameters:
|
|
|
|
|
|
# k - number of Kalman filter iteration
|
|
|
|
|
|
# m - vector for calculating matrices. Required for EKF. Not used here.
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# f_hl = lambda k,m,H: np.dot(H, m)
|
|
|
|
|
|
# f_H = lambda k,m,P: p_H[:,:, index[H_time_var_index, k]]
|
2015-08-10 19:40:39 +03:00
|
|
|
|
#f_R = lambda k: p_R[:,:, index[R_time_var_index, k]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#o_R = R_handling( p_R, index, R_time_var_index, 20)
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
dF = cls._check_grad_state_matrices(grad_calc_params.get('dF'), state_dim, grad_params_no, which = 'dA')
|
2015-10-08 15:30:34 +03:00
|
|
|
|
dQc = cls._check_grad_state_matrices(grad_calc_params.get('dQc'), state_dim, grad_params_no, which = 'dQ')
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dP_inf = cls._check_grad_state_matrices(grad_calc_params.get('dP_inf'), state_dim, grad_params_no, which = 'dA')
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dH = cls._check_grad_measurement_matrices(grad_calc_params.get('dH'), state_dim, grad_params_no, measurement_dim, which = 'dH')
|
|
|
|
|
|
dR = cls._check_grad_measurement_matrices(grad_calc_params.get('dR'), state_dim, grad_params_no, measurement_dim, which = 'dR')
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dm_init = grad_calc_params.get('dm_init') # Initial values for the Kalman Filter
|
|
|
|
|
|
if dm_init is None:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
# multiple time series mode. Keep grad_params always as a last dimension
|
|
|
|
|
|
dm_init = np.zeros( (state_dim, time_series_no, grad_params_no) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dP_init = grad_calc_params.get('dP_init') # Initial values for the Kalman Filter
|
|
|
|
|
|
if dP_init is None:
|
|
|
|
|
|
dP_init = dP_inf(0).copy() # get the dP_init matrix, because now it is a function
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
|
|
|
|
|
dP_inf = None
|
|
|
|
|
|
dF = None
|
|
|
|
|
|
dQc = None
|
|
|
|
|
|
dH = None
|
|
|
|
|
|
dR = None
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dm_init = None
|
|
|
|
|
|
dP_init = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
measurement_callables = Std_Measurement_Callables_Class(p_H, H_time_var_index, p_R, index, R_time_var_index, 20, dH, dR)
|
|
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
dynamic_callables = cls._cont_to_discrete_object(X, F, L, Qc, compute_derivatives=calc_grad_log_likelihood,
|
|
|
|
|
|
grad_params_no=grad_params_no,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
P_inf=P_inf, dP_inf=dP_inf, dF = dF, dQc=dQc)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if print_verbose:
|
|
|
|
|
|
print("General: run Continuos-Discrete Kalman Filter")
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Also for dH, dR and probably for all derivatives
|
2015-09-04 19:44:19 +03:00
|
|
|
|
(M, P, log_likelihood, grad_log_likelihood, AQcomp) = cls._cont_discr_kalman_filter_raw(state_dim,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dynamic_callables, measurement_callables,
|
|
|
|
|
|
X, Y, m_init=m_init, P_init=P_init,
|
|
|
|
|
|
p_kalman_filter_type=p_kalman_filter_type,
|
|
|
|
|
|
calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood, grad_params_no=grad_params_no,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
dm_init=dm_init, dP_init=dP_init)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_index_shape is not None:
|
|
|
|
|
|
index.shape = old_index_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_X_shape is not None:
|
|
|
|
|
|
X.shape = old_X_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_Y_shape is not None:
|
|
|
|
|
|
Y.shape = old_Y_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_H_shape is not None:
|
|
|
|
|
|
p_H.shape = old_H_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if old_R_shape is not None:
|
|
|
|
|
|
p_R.shape = old_R_shape
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
return (M, P, log_likelihood, grad_log_likelihood, AQcomp)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@classmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def _cont_discr_kalman_filter_raw(cls,state_dim, p_dynamic_callables, p_measurement_callables, X, Y,
|
|
|
|
|
|
m_init, P_init,
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_kalman_filter_type='regular',
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_log_likelihood=False,
|
|
|
|
|
|
calc_grad_log_likelihood=False, grad_params_no=None,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
dm_init=None, dP_init=None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
General filtering algorithm for inference in the continuos-discrete
|
2015-07-14 16:44:21 +03:00
|
|
|
|
state-space model:
|
|
|
|
|
|
|
|
|
|
|
|
d/dt x(t) = F * x(t) + L * w(t); w(t) ~ N(0, Qc)
|
|
|
|
|
|
y_{k} = H_{k} * x_{k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Returns estimated filter distributions x_{k} ~ N(m_{k}, P(k))
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Current Features:
|
|
|
|
|
|
----------------------------------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
1) Function support "multiple time series mode" which means that exactly
|
|
|
|
|
|
the same State-Space model is used to filter several sets of measurements.
|
|
|
|
|
|
In this case third dimension of Y should include these state-space measurements
|
|
|
|
|
|
Log_likelihood and Grad_log_likelihood have the corresponding dimensions then.
|
|
|
|
|
|
|
|
|
|
|
|
2) Measurement may include missing values. In this case update step is
|
|
|
|
|
|
not done for this measurement. (later may be changed)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Input:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
-----------------
|
|
|
|
|
|
state_dim: int
|
|
|
|
|
|
Demensionality of the states
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
F: (state_dim, state_dim) matrix
|
|
|
|
|
|
F in the model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
L: (state_dim, noise_dim) matrix
|
|
|
|
|
|
L in the model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Qc: (noise_dim, noise_dim) matrix
|
|
|
|
|
|
Q_c in the model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_inf: (state_dim, state_dim) matrix
|
|
|
|
|
|
State varince matrix on infinity.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
p_h: function (k, x_{k}, H_{k}). Measurement function.
|
|
|
|
|
|
k (iteration number),
|
|
|
|
|
|
x_{k}
|
|
|
|
|
|
H_{k} Jacobian matrices of f_h. In the linear case it is exactly H_{k}.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
f_H: function (k, m, P) return Jacobian of dynamic function, it is
|
|
|
|
|
|
passed into p_h.
|
|
|
|
|
|
k (iteration number),
|
|
|
|
|
|
m: point where Jacobian is evaluated,
|
|
|
|
|
|
P: parameter for Jacobian, usually covariance matrix.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
p_f_R: function (k). Returns noise matrix of measurement equation
|
2015-07-14 16:44:21 +03:00
|
|
|
|
on iteration k.
|
|
|
|
|
|
k (iteration number).
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
m_init: vector or matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Initial distribution mean. For "multiple time series mode"
|
2015-07-14 16:44:21 +03:00
|
|
|
|
it is matrix, second dimension of which correspond to different
|
2016-03-15 17:01:05 +02:00
|
|
|
|
time series. In regular case ("one time series mode") it is a
|
2015-07-14 16:44:21 +03:00
|
|
|
|
vector.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_init: matrix or scalar
|
|
|
|
|
|
Initial covariance of the states. Must be not None
|
|
|
|
|
|
"multiple time series mode" does not affect it, since it does not
|
|
|
|
|
|
affect anything related to state variaces.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
p_kalman_filter_type: string, one of ('regular', 'svd')
|
|
|
|
|
|
Which Kalman Filter is used. Regular or SVD. SVD is more numerically
|
|
|
|
|
|
stable, in particular, Covariace matrices are guarantied to be
|
|
|
|
|
|
positive semi-definite. However, 'svd' works slower, especially for
|
|
|
|
|
|
small data due to SVD call overhead.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
calc_log_likelihood: boolean
|
|
|
|
|
|
Whether to calculate marginal likelihood of the state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
calc_grad_log_likelihood: boolean
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Whether to calculate gradient of the marginal likelihood
|
|
|
|
|
|
of the state-space model. If true then the next parameter must
|
2015-07-14 16:44:21 +03:00
|
|
|
|
provide the extra parameters for gradient calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no: int
|
|
|
|
|
|
Number of gradient parameters
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dP_inf, dF, dQc, dH, dR, dm_init, dP_init: matrices or 3D arrays.
|
|
|
|
|
|
Necessary parameters for derivatives calculation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2015-07-14 16:44:21 +03:00
|
|
|
|
steps_no = Y.shape[0] # number of steps in the Kalman Filter
|
2015-09-04 19:44:19 +03:00
|
|
|
|
time_series_no = Y.shape[2] # multiple time series mode
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Allocate space for results
|
|
|
|
|
|
# Mean estimations. Initial values will be included
|
2015-09-04 19:44:19 +03:00
|
|
|
|
M = np.empty(((steps_no+1),state_dim,time_series_no))
|
|
|
|
|
|
M[0,:,:] = m_init # Initialize mean values
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Variance estimations. Initial values will be included
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P = np.empty(((steps_no+1),state_dim,state_dim))
|
2015-08-10 19:40:39 +03:00
|
|
|
|
P_init = 0.5*( P_init + P_init.T) # symmetrize initial covariance. In some ustable cases this is uiseful
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P[0,:,:] = P_init # Initialize initial covariance matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
#import pdb;pdb.set_trace()
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2016-03-15 17:01:05 +02:00
|
|
|
|
(U,S,Vh) = sp.linalg.svd( P_init,full_matrices=False, compute_uv=True,
|
2015-08-10 19:40:39 +03:00
|
|
|
|
overwrite_a=False,check_finite=True)
|
|
|
|
|
|
S[ (S==0) ] = 1e-17 # allows to run algorithm for singular initial variance
|
|
|
|
|
|
P_upd = (P_init, S,U)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
#log_likelihood = 0
|
|
|
|
|
|
#grad_log_likelihood = np.zeros((grad_params_no,1))
|
|
|
|
|
|
log_likelihood = 0 if calc_log_likelihood else None
|
|
|
|
|
|
grad_log_likelihood = 0 if calc_grad_log_likelihood else None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
#setting initial values for derivatives update
|
|
|
|
|
|
dm_upd = dm_init
|
|
|
|
|
|
dP_upd = dP_init
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Main loop of the Kalman filter
|
|
|
|
|
|
for k in range(0,steps_no):
|
|
|
|
|
|
# In this loop index for new estimations is (k+1), old - (k)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
# This happened because initial values are stored at 0-th index.
|
2015-08-10 19:40:39 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
prev_mean = M[k,:,:] # mean from the previous step
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2015-08-10 19:40:39 +03:00
|
|
|
|
m_pred, P_pred, dm_pred, dP_pred = \
|
2015-09-04 19:44:19 +03:00
|
|
|
|
cls._kalman_prediction_step_SVD(k, prev_mean ,P_upd, p_dynamic_callables,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
p_dm = dm_upd, p_dP = dP_upd)
|
2015-08-10 19:40:39 +03:00
|
|
|
|
else:
|
|
|
|
|
|
m_pred, P_pred, dm_pred, dP_pred = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_prediction_step(k, prev_mean ,P[k,:,:], p_dynamic_callables,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_dm = dm_upd, p_dP = dP_upd )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2015-09-04 19:44:19 +03:00
|
|
|
|
k_measurment = Y[k,:,:]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if (np.any(np.isnan(k_measurment)) == False):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2015-10-08 15:30:34 +03:00
|
|
|
|
m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_update_step_SVD(k, m_pred , P_pred, p_measurement_callables,
|
|
|
|
|
|
k_measurment, calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
p_dm = dm_pred, p_dP = dP_pred )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
# cls._kalman_update_step(k, m_pred , P_pred[0], f_h, f_H, p_R.f_R, k_measurment,
|
|
|
|
|
|
# calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
# calc_grad_log_likelihood=calc_grad_log_likelihood,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# p_dm = dm_pred, p_dP = dP_pred, grad_calc_params_2 = (dH, dR))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
#
|
|
|
|
|
|
# (U,S,Vh) = sp.linalg.svd( P_upd,full_matrices=False, compute_uv=True,
|
2015-10-08 15:30:34 +03:00
|
|
|
|
# overwrite_a=False,check_finite=True)
|
|
|
|
|
|
# P_upd = (P_upd, S,U)
|
|
|
|
|
|
else:
|
|
|
|
|
|
m_upd, P_upd, log_likelihood_update, dm_upd, dP_upd, d_log_likelihood_update = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_update_step(k, m_pred , P_pred, p_measurement_callables, k_measurment,
|
|
|
|
|
|
calc_log_likelihood=calc_log_likelihood,
|
|
|
|
|
|
calc_grad_log_likelihood=calc_grad_log_likelihood,
|
|
|
|
|
|
p_dm = dm_pred, p_dP = dP_pred )
|
2015-08-10 19:40:39 +03:00
|
|
|
|
else:
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if k_measurment.shape != (1,1):
|
|
|
|
|
|
raise ValueError("Nan measurements are currently not supported for \
|
|
|
|
|
|
multidimensional output and multiple tiem series.")
|
|
|
|
|
|
else:
|
|
|
|
|
|
m_upd = m_pred; P_upd = P_pred; dm_upd = dm_pred; dP_upd = dP_pred
|
|
|
|
|
|
log_likelihood_update = 0.0;
|
|
|
|
|
|
d_log_likelihood_update = 0.0;
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_log_likelihood:
|
|
|
|
|
|
log_likelihood += log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if calc_grad_log_likelihood:
|
|
|
|
|
|
grad_log_likelihood += d_log_likelihood_update
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
M[k+1,:,:] = m_upd # separate mean value for each time series
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
if p_kalman_filter_type == 'svd':
|
2015-08-10 19:40:39 +03:00
|
|
|
|
P[k+1,:,:] = P_upd[0]
|
|
|
|
|
|
else:
|
|
|
|
|
|
P[k+1,:,:] = P_upd
|
2015-07-14 16:44:21 +03:00
|
|
|
|
#print("kf it: %i" % k)
|
|
|
|
|
|
# !!!Print statistics! Print sizes of matrices
|
|
|
|
|
|
# !!!Print statistics! Print iteration time base on another boolean variable
|
2015-09-04 19:44:19 +03:00
|
|
|
|
return (M, P, log_likelihood, grad_log_likelihood, p_dynamic_callables.reset(False))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def cont_discr_rts_smoother(cls,state_dim, filter_means, filter_covars,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_dynamic_callables=None, X=None, F=None,L=None,Qc=None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Continuos-discrete Rauch–Tung–Striebel(RTS) smoother.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
This function implements Rauch–Tung–Striebel(RTS) smoother algorithm
|
2015-07-14 16:44:21 +03:00
|
|
|
|
based on the results of _cont_discr_kalman_filter_raw.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Model:
|
|
|
|
|
|
d/dt x(t) = F * x(t) + L * w(t); w(t) ~ N(0, Qc)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
y_{k} = H_{k} * x_{k} + r_{k}; r_{k-1} ~ N(0, R_{k})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Returns estimated smoother distributions x_{k} ~ N(m_{k}, P(k))
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
filter_means: (no_steps+1,state_dim) matrix or (no_steps+1,state_dim, time_series_no) 3D array
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Results of the Kalman Filter means estimation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
filter_covars: (no_steps+1, state_dim, state_dim) 3D array
|
|
|
|
|
|
Results of the Kalman Filter covariance estimation.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Dynamic_callables: object or None
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Object form the filter phase which provides functions for computing
|
|
|
|
|
|
A, Q, dA, dQ fro discrete model from the continuos model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
X, F, L, Qc: matrices
|
2015-10-08 15:30:34 +03:00
|
|
|
|
If AQcomp is None, these matrices are used to create this object from scratch.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
-------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
M: (no_steps+1,state_dim) matrix
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Smoothed estimates of the state means
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P: (no_steps+1,state_dim, state_dim) 3D array
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Smoothed estimates of the state covariances
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
f_a = lambda k,m,A: np.dot(A, m) # state dynamic model
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if p_dynamic_callables is None: # make this object from scratch
|
2016-03-15 17:01:05 +02:00
|
|
|
|
p_dynamic_callables = cls._cont_to_discrete_object(cls, X, F,L,Qc,f_a,compute_derivatives=False,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
grad_params_no=None, P_inf=None, dP_inf=None, dF = None, dQc=None)
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
no_steps = filter_covars.shape[0]-1# number of steps (minus initial covariance)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
M = np.empty(filter_means.shape) # smoothed means
|
|
|
|
|
|
P = np.empty(filter_covars.shape) # smoothed covars
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if print_verbose:
|
|
|
|
|
|
print("General: run Continuos-Discrete Kalman Smoother")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
M[-1,:,:] = filter_means[-1,:,:]
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P[-1,:,:] = filter_covars[-1,:,:]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
for k in range(no_steps-1,-1,-1):
|
|
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
prev_mean = filter_means[k,:] # mean from the previous step
|
2015-04-07 18:20:11 +03:00
|
|
|
|
m_pred, P_pred, tmp1, tmp2 = \
|
2016-03-15 17:01:05 +02:00
|
|
|
|
cls._kalman_prediction_step(k, prev_mean,
|
|
|
|
|
|
filter_covars[k,:,:], p_dynamic_callables,
|
|
|
|
|
|
calc_grad_log_likelihood=False)
|
2015-09-04 19:44:19 +03:00
|
|
|
|
p_m = filter_means[k,:]
|
|
|
|
|
|
p_m_prev_step = M[(k+1),:]
|
|
|
|
|
|
|
2016-03-15 17:01:05 +02:00
|
|
|
|
m_upd, P_upd, tmp_G = cls._rts_smoother_update_step(k,
|
|
|
|
|
|
p_m ,filter_covars[k,:,:],
|
2015-09-04 19:44:19 +03:00
|
|
|
|
m_pred, P_pred, p_m_prev_step ,P[(k+1),:,:], p_dynamic_callables)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
M[k,:,:] = m_upd
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P[k,:,:] = P_upd
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Return values
|
|
|
|
|
|
return (M, P)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@classmethod
|
2015-07-14 16:44:21 +03:00
|
|
|
|
def _cont_to_discrete_object(cls, X, F, L, Qc, compute_derivatives=False,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
grad_params_no=None,
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_inf=None, dP_inf=None, dF = None, dQc=None):
|
2015-04-07 18:20:11 +03:00
|
|
|
|
"""
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Function return the object which is used in Kalman filter and/or
|
|
|
|
|
|
smoother to obtain matrices A, Q and their derivatives for discrete model
|
|
|
|
|
|
from the continuous model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
There are 2 objects AQcompute_once and AQcompute_batch and the function
|
2015-07-14 16:44:21 +03:00
|
|
|
|
returs the appropriate one based on the number of different time steps.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
----------------------
|
|
|
|
|
|
X, F, L, Qc: matrices
|
|
|
|
|
|
Continuous model matrices
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
f_a: function
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Dynamic Function is attached to the Dynamic_Model_Callables class
|
2015-07-14 16:44:21 +03:00
|
|
|
|
compute_derivatives: boolean
|
|
|
|
|
|
Whether to compute derivatives
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no: int
|
|
|
|
|
|
Number of parameters in the gradient
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_inf, dP_inf, dF, dQ: matrices and 3D objects
|
|
|
|
|
|
Data necessary to compute derivatives.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------------------
|
|
|
|
|
|
AQcomp: object
|
|
|
|
|
|
Its methods return matrices (and optionally derivatives) for the
|
|
|
|
|
|
discrete state-space model.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
unique_round_decimals = 10
|
|
|
|
|
|
threshold_number_of_unique_time_steps = 20 # above which matrices are separately each time
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dt = np.empty((X.shape[0],))
|
|
|
|
|
|
dt[1:] = np.diff(X[:,0],axis=0)
|
2015-04-08 19:44:19 +03:00
|
|
|
|
dt[0] = 0#dt[1]
|
2015-04-07 18:20:11 +03:00
|
|
|
|
unique_indices = np.unique(np.round(dt, decimals=unique_round_decimals))
|
2015-07-14 16:44:21 +03:00
|
|
|
|
number_unique_indices = len(unique_indices)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
#import pdb; pdb.set_trace()
|
2015-10-08 15:30:34 +03:00
|
|
|
|
if use_cython:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
class AQcompute_batch(state_space_cython.AQcompute_batch_Cython):
|
|
|
|
|
|
def __init__(self, F,L,Qc,dt,compute_derivatives=False, grad_params_no=None, P_inf=None, dP_inf=None, dF = None, dQc=None):
|
|
|
|
|
|
As, Qs, reconstruct_indices, dAs, dQs = ContDescrStateSpace.lti_sde_to_descrete(F,
|
2016-03-15 17:01:05 +02:00
|
|
|
|
L,Qc,dt,compute_derivatives,
|
2015-09-04 19:44:19 +03:00
|
|
|
|
grad_params_no=grad_params_no, P_inf=P_inf, dP_inf=dP_inf, dF=dF, dQc=dQc)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
super(AQcompute_batch,self).__init__(As, Qs, reconstruct_indices, dAs,dQs)
|
|
|
|
|
|
else:
|
|
|
|
|
|
AQcompute_batch = cls.AQcompute_batch_Python
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-09-04 19:44:19 +03:00
|
|
|
|
if number_unique_indices > threshold_number_of_unique_time_steps:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
AQcomp = cls.AQcompute_once(F,L,Qc, dt,compute_derivatives=compute_derivatives,
|
|
|
|
|
|
grad_params_no=grad_params_no, P_inf=P_inf, dP_inf=dP_inf, dF=dF, dQc=dQc)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if print_verbose:
|
|
|
|
|
|
print("CDO: Continue-to-discrete INSTANTANEOUS object is created.")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
print("CDO: Number of different time steps: %i" % (number_unique_indices,) )
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
else:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
AQcomp = AQcompute_batch(F,L,Qc,dt,compute_derivatives=compute_derivatives,
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no=grad_params_no, P_inf=P_inf, dP_inf=dP_inf, dF=dF, dQc=dQc)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
if print_verbose:
|
2015-07-14 16:44:21 +03:00
|
|
|
|
print("CDO: Continue-to-discrete BATCH object is created.")
|
|
|
|
|
|
print("CDO: Number of different time steps: %i" % (number_unique_indices,) )
|
2016-03-15 17:01:05 +02:00
|
|
|
|
print("CDO: Total size if its data: %i" % (AQcomp.total_size_of_data,) )
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
return AQcomp
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
@staticmethod
|
2016-03-15 17:01:05 +02:00
|
|
|
|
def lti_sde_to_descrete(F,L,Qc,dt,compute_derivatives=False,
|
|
|
|
|
|
grad_params_no=None, P_inf=None,
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dP_inf=None, dF = None, dQc=None):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Linear Time-Invariant Stochastic Differential Equation (LTI SDE):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dx(t) = F x(t) dt + L d \beta ,where
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
x(t): (vector) stochastic process
|
|
|
|
|
|
\beta: (vector) Brownian motion process
|
2016-03-15 17:01:05 +02:00
|
|
|
|
F, L: (time invariant) matrices of corresponding dimensions
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Qc: covariance of noise.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
This function rewrites it into the corresponding state-space form:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
x_{k} = A_{k} * x_{k-1} + q_{k-1}; q_{k-1} ~ N(0, Q_{k-1})
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
F,L: LTI SDE matrices of corresponding dimensions
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
Qc: matrix (n,n)
|
|
|
|
|
|
Covarince between different dimensions of noise \beta.
|
2015-04-07 18:20:11 +03:00
|
|
|
|
n is the dimensionality of the noise.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dt: double or iterable
|
|
|
|
|
|
Time difference used on this iteration.
|
|
|
|
|
|
If dt is iterable, then A and Q_noise are computed for every
|
2016-03-15 17:01:05 +02:00
|
|
|
|
unique dt
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
compute_derivatives: boolean
|
|
|
|
|
|
Whether derivatives of A and Q are required.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
grad_params_no: int
|
|
|
|
|
|
Number of gradient parameters
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P_inf: (state_dim. state_dim) matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dP_inf
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dF: 3D array
|
|
|
|
|
|
Derivatives of F
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dQc: 3D array
|
|
|
|
|
|
Derivatives of Qc
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dR: 3D array
|
|
|
|
|
|
Derivatives of R
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
--------------
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
A: matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
A_{k}. Because we have LTI SDE only dt can affect on matrix
|
2015-04-07 18:20:11 +03:00
|
|
|
|
difference for different k.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Q_noise: matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Covariance matrix of (vector) q_{k-1}. Only dt can affect the
|
2015-04-07 18:20:11 +03:00
|
|
|
|
matrix difference for different k.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
reconstruct_index: array
|
|
|
|
|
|
If dt was iterable return three dimensinal arrays A and Q_noise.
|
|
|
|
|
|
Third dimension of these arrays correspond to unique dt's.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
This reconstruct_index contain indices of the original dt's
|
2015-04-07 18:20:11 +03:00
|
|
|
|
in the uninue dt sequence. A[:,:, reconstruct_index[5]]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
is matrix A of 6-th(indices start from zero) dt in the original
|
2015-07-14 16:44:21 +03:00
|
|
|
|
sequence.
|
|
|
|
|
|
dA: 3D array
|
|
|
|
|
|
Derivatives of A
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dQ: 3D array
|
|
|
|
|
|
Derivatives of Q
|
2016-03-15 17:01:05 +02:00
|
|
|
|
"""
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Dimensionality
|
|
|
|
|
|
n = F.shape[0]
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if not isinstance(dt, collections.Iterable): # not iterable, scalar
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# The dynamical model
|
2016-03-15 17:01:05 +02:00
|
|
|
|
A = matrix_exponent(F*dt)
|
2015-04-08 19:44:19 +03:00
|
|
|
|
if np.any( np.isnan(A)):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
A = linalg.expm3(F*dt)
|
|
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# The covariance matrix Q by matrix fraction decomposition ->
|
|
|
|
|
|
Phi = np.zeros((2*n,2*n))
|
|
|
|
|
|
Phi[:n,:n] = F
|
|
|
|
|
|
Phi[:n,n:] = L.dot(Qc).dot(L.T)
|
|
|
|
|
|
Phi[n:,n:] = -F.T
|
2015-07-14 16:44:21 +03:00
|
|
|
|
AB = matrix_exponent(Phi*dt)
|
2015-04-08 19:44:19 +03:00
|
|
|
|
AB = np.dot(AB, np.vstack((np.zeros((n,n)),np.eye(n))))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
Q_noise_1 = linalg.solve(AB[n:,:].T,AB[:n,:].T)
|
2016-04-06 17:35:08 +03:00
|
|
|
|
Q_noise_2 = P_inf - A.dot(P_inf).dot(A.T)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# The covariance matrix Q by matrix fraction decomposition <-
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
if compute_derivatives:
|
|
|
|
|
|
dA = np.zeros([n, n, grad_params_no])
|
|
|
|
|
|
dQ = np.zeros([n, n, grad_params_no])
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
#AA = np.zeros([2*n, 2*n, nparam])
|
|
|
|
|
|
FF = np.zeros([2*n, 2*n])
|
|
|
|
|
|
AA = np.zeros([2*n, 2*n, grad_params_no])
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
for p in range(0, grad_params_no):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
FF[:n,:n] = F
|
|
|
|
|
|
FF[n:,:n] = dF[:,:,p]
|
|
|
|
|
|
FF[n:,n:] = F
|
|
|
|
|
|
|
|
|
|
|
|
# Solve the matrix exponential
|
2015-07-14 16:44:21 +03:00
|
|
|
|
AA[:,:,p] = matrix_exponent(FF*dt)
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
|
|
|
|
|
# Solve the differential equation
|
|
|
|
|
|
#foo = AA[:,:,p].dot(np.vstack([m, dm[:,p]]))
|
|
|
|
|
|
#mm = foo[:n,:]
|
|
|
|
|
|
#dm[:,p] = foo[n:,:]
|
|
|
|
|
|
|
|
|
|
|
|
# The discrete-time dynamical model*
|
|
|
|
|
|
if p==0:
|
|
|
|
|
|
A = AA[:n,:n,p]
|
|
|
|
|
|
Q_noise_2 = P_inf - A.dot(P_inf).dot(A.T)
|
|
|
|
|
|
Q_noise = Q_noise_2
|
|
|
|
|
|
#PP = A.dot(P).dot(A.T) + Q_noise_2
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# The derivatives of A and Q
|
|
|
|
|
|
dA[:,:,p] = AA[n:,:n,p]
|
|
|
|
|
|
dQ[:,:,p] = dP_inf[:,:,p] - dA[:,:,p].dot(P_inf).dot(A.T) \
|
|
|
|
|
|
- A.dot(dP_inf[:,:,p]).dot(A.T) - A.dot(P_inf).dot(dA[:,:,p].T) # Rewrite not ro multiply two times
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
else:
|
2015-04-07 18:20:11 +03:00
|
|
|
|
dA = None
|
|
|
|
|
|
dQ = None
|
2016-04-06 17:35:08 +03:00
|
|
|
|
Q_noise = Q_noise_2
|
|
|
|
|
|
# Innacuracies have been observed when Q_noise_1 was used.
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
#Q_noise = Q_noise_1
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
|
|
|
|
|
# Return
|
|
|
|
|
|
return A, Q_noise,None, dA, dQ
|
|
|
|
|
|
|
|
|
|
|
|
else: # iterable, array
|
|
|
|
|
|
|
|
|
|
|
|
# Time discretizations (round to 14 decimals to avoid problems)
|
|
|
|
|
|
dt_unique, tmp, reconstruct_index = np.unique(np.round(dt,8),
|
|
|
|
|
|
return_index=True,return_inverse=True)
|
|
|
|
|
|
del tmp
|
|
|
|
|
|
# Allocate space for A and Q
|
|
|
|
|
|
A = np.empty((n,n,dt_unique.shape[0]))
|
|
|
|
|
|
Q_noise = np.empty((n,n,dt_unique.shape[0]))
|
|
|
|
|
|
|
|
|
|
|
|
if compute_derivatives:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dA = np.empty((n,n,grad_params_no,dt_unique.shape[0]))
|
|
|
|
|
|
dQ = np.empty((n,n,grad_params_no,dt_unique.shape[0]))
|
2015-07-14 16:44:21 +03:00
|
|
|
|
else:
|
|
|
|
|
|
dA = None
|
|
|
|
|
|
dQ = None
|
2015-04-07 18:20:11 +03:00
|
|
|
|
# Call this function for each unique dt
|
|
|
|
|
|
for j in range(0,dt_unique.shape[0]):
|
2015-07-14 16:44:21 +03:00
|
|
|
|
A[:,:,j], Q_noise[:,:,j], tmp1, dA_t, dQ_t = ContDescrStateSpace.lti_sde_to_descrete(F,L,Qc,dt_unique[j],
|
2015-04-07 18:20:11 +03:00
|
|
|
|
compute_derivatives=compute_derivatives, grad_params_no=grad_params_no, P_inf=P_inf, dP_inf=dP_inf, dF = dF, dQc=dQc)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if compute_derivatives:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
dA[:,:,:,j] = dA_t
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dQ[:,:,:,j] = dQ_t
|
2015-04-07 18:20:11 +03:00
|
|
|
|
|
|
|
|
|
|
# Return
|
2015-07-14 16:44:21 +03:00
|
|
|
|
return A, Q_noise, reconstruct_index, dA, dQ
|
|
|
|
|
|
|
|
|
|
|
|
def matrix_exponent(M):
|
|
|
|
|
|
"""
|
|
|
|
|
|
The function computes matrix exponent and handles some special cases
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
if (M.shape[0] == 1): # 1*1 matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
Mexp = np.array( ((np.exp(M[0,0]) ,),) )
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
else: # matrix is larger
|
2015-09-04 19:44:19 +03:00
|
|
|
|
method = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
Mexp = linalg.expm(M)
|
|
|
|
|
|
method = 1
|
2015-10-21 15:47:03 +03:00
|
|
|
|
except (Exception,) as e:
|
2015-09-04 19:44:19 +03:00
|
|
|
|
Mexp = linalg.expm3(M)
|
|
|
|
|
|
method = 2
|
|
|
|
|
|
finally:
|
|
|
|
|
|
if np.any(np.isnan(Mexp)):
|
|
|
|
|
|
if method == 2:
|
|
|
|
|
|
raise ValueError("Matrix Exponent is not computed 1")
|
|
|
|
|
|
else:
|
|
|
|
|
|
Mexp = linalg.expm3(M)
|
|
|
|
|
|
method = 2
|
|
|
|
|
|
if np.any(np.isnan(Mexp)):
|
|
|
|
|
|
raise ValueError("Matrix Exponent is not computed 2")
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
return Mexp
|
|
|
|
|
|
|
|
|
|
|
|
def balance_matrix(A):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Balance matrix, i.e. finds such similarity transformation of the original
|
|
|
|
|
|
matrix A: A = T * bA * T^{-1}, where norms of columns of bA and of rows of bA
|
|
|
|
|
|
are as close as possible. It is usually used as a preprocessing step in
|
|
|
|
|
|
eigenvalue calculation routine. It is useful also for State-Space models.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
See also:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
[1] Beresford N. Parlett and Christian Reinsch (1969). Balancing
|
|
|
|
|
|
a matrix for calculation of eigenvalues and eigenvectors.
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Numerische Mathematik, 13(4): 293-304.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
----------------------
|
|
|
|
|
|
A: square matrix
|
|
|
|
|
|
Matrix to be balanced
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Output:
|
|
|
|
|
|
----------------
|
|
|
|
|
|
bA: matrix
|
|
|
|
|
|
Balanced matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
T: matrix
|
|
|
|
|
|
Left part of the similarity transformation
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
T_inv: matrix
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Right part of the similarity transformation.
|
|
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if len(A.shape) != 2 or (A.shape[0] != A.shape[1]):
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError('balance_matrix: Expecting square matrix')
|
|
|
|
|
|
|
|
|
|
|
|
N = A.shape[0] # matrix size
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
gebal = sp.linalg.lapack.get_lapack_funcs('gebal',(A,))
|
|
|
|
|
|
bA, lo, hi, pivscale, info = gebal(A, permute=True, scale=True,overwrite_a=False)
|
|
|
|
|
|
if info < 0:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
raise ValueError('balance_matrix: Illegal value in %d-th argument of internal gebal ' % -info)
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# calculating the similarity transforamtion:
|
|
|
|
|
|
def perm_matr(D, c1,c2):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Function creates the permutation matrix which swaps columns c1 and c2.
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Input:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
D: int
|
|
|
|
|
|
Size of the permutation matrix
|
|
|
|
|
|
c1: int
|
|
|
|
|
|
Column 1. Numeration starts from 1...D
|
|
|
|
|
|
c2: int
|
|
|
|
|
|
Column 2. Numeration starts from 1...D
|
|
|
|
|
|
"""
|
|
|
|
|
|
i1 = c1-1; i2 = c2-1 # indices
|
2016-03-15 17:01:05 +02:00
|
|
|
|
P = np.eye(D);
|
|
|
|
|
|
P[i1,i1] = 0.0; P[i2,i2] = 0.0; # nullify diagonal elements
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P[i1,i2] = 1.0; P[i2,i1] = 1.0
|
|
|
|
|
|
|
|
|
|
|
|
return P
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
P = np.eye(N) # permutation matrix
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if (hi != N-1): # there are row permutations
|
2016-03-15 17:01:05 +02:00
|
|
|
|
for k in range(N-1,hi,-1):
|
|
|
|
|
|
new_perm = perm_matr(N, k+1, pivscale[k])
|
2015-07-14 16:44:21 +03:00
|
|
|
|
P = np.dot(P,new_perm)
|
|
|
|
|
|
if (lo != 0):
|
|
|
|
|
|
for k in range(0,lo,1):
|
|
|
|
|
|
new_perm = perm_matr(N, k+1, pivscale[k])
|
|
|
|
|
|
P = np.dot(P,new_perm)
|
|
|
|
|
|
D = pivscale.copy()
|
|
|
|
|
|
D[0:lo] = 1.0; D[hi+1:N] = 1.0 # thesee scaling factors must be set to one.
|
|
|
|
|
|
#D = np.diag(D) # make a diagonal matrix
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
T = np.dot(P,np.diag(D)) # similarity transformation in question
|
|
|
|
|
|
T_inv = np.dot(np.diag(D**(-1)),P.T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
#print( np.max(A - np.dot(T, np.dot(bA, T_inv) )) )
|
|
|
|
|
|
return bA.copy(), T, T_inv
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
def balance_ss_model(F,L,Qc,H,Pinf,P0,dF=None,dQc=None,dPinf=None,dP0=None):
|
2015-07-14 16:44:21 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Balances State-Space model for more numerical stability
|
|
|
|
|
|
|
|
|
|
|
|
This is based on the following:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dx/dt = F x + L w
|
|
|
|
|
|
y = H x
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
Let T z = x, which gives
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
dz/dt = inv(T) F T z + inv(T) L w
|
2016-03-15 17:01:05 +02:00
|
|
|
|
y = H T z
|
2015-07-14 16:44:21 +03:00
|
|
|
|
"""
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
bF,T,T_inv = balance_matrix(F)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
bL = np.dot( T_inv, L)
|
|
|
|
|
|
bQc = Qc # not affected
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
bH = np.dot(H, T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
bPinf = np.dot(T_inv, np.dot(Pinf, T_inv.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
#import pdb; pdb.set_trace()
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# LL,islower = linalg.cho_factor(Pinf)
|
|
|
|
|
|
# inds = np.triu_indices(Pinf.shape[0],k=1)
|
|
|
|
|
|
# LL[inds] = 0.0
|
|
|
|
|
|
# bLL = np.dot(T_inv, LL)
|
|
|
|
|
|
# bPinf = np.dot( bLL, bLL.T)
|
|
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
bP0 = np.dot(T_inv, np.dot(P0, T_inv.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if dF is not None:
|
|
|
|
|
|
bdF = np.zeros(dF.shape)
|
|
|
|
|
|
for i in range(dF.shape[2]):
|
|
|
|
|
|
bdF[:,:,i] = np.dot( T_inv, np.dot( dF[:,:,i], T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
else:
|
|
|
|
|
|
bdF = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
if dPinf is not None:
|
|
|
|
|
|
bdPinf = np.zeros(dPinf.shape)
|
|
|
|
|
|
for i in range(dPinf.shape[2]):
|
|
|
|
|
|
bdPinf[:,:,i] = np.dot( T_inv, np.dot( dPinf[:,:,i], T_inv.T))
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
# LL,islower = linalg.cho_factor(dPinf[:,:,i])
|
|
|
|
|
|
# inds = np.triu_indices(dPinf[:,:,i].shape[0],k=1)
|
|
|
|
|
|
# LL[inds] = 0.0
|
|
|
|
|
|
# bLL = np.dot(T_inv, LL)
|
|
|
|
|
|
# bdPinf[:,:,i] = np.dot( bLL, bLL.T)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
else:
|
|
|
|
|
|
bdPinf = None
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
if dP0 is not None:
|
|
|
|
|
|
bdP0 = np.zeros(dP0.shape)
|
|
|
|
|
|
for i in range(dP0.shape[2]):
|
|
|
|
|
|
bdP0[:,:,i] = np.dot( T_inv, np.dot( dP0[:,:,i], T_inv.T))
|
|
|
|
|
|
else:
|
2016-03-15 17:01:05 +02:00
|
|
|
|
bdP0 = None
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-07-14 16:44:21 +03:00
|
|
|
|
bdQc = dQc # not affected
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2015-08-10 19:40:39 +03:00
|
|
|
|
# (F,L,Qc,H,Pinf,P0,dF,dQc,dPinf,dP0)
|
2016-03-15 17:01:05 +02:00
|
|
|
|
|
2016-04-06 17:35:08 +03:00
|
|
|
|
return bF, bL, bQc, bH, bPinf, bP0, bdF, bdQc, bdPinf, bdP0, T
|