Skip to content

Commit ee07af3

Browse files
authored
Merge pull request #60 from jeiloh/master
Kalman filter upgrade
2 parents 0328cc5 + 0860e0b commit ee07af3

3 files changed

Lines changed: 80 additions & 23 deletions

File tree

pipedream_solver/nutils.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
from pipedream_solver.utils import _square_root_kalman_semi_implicit
23
from numba import njit
34

45
@njit
@@ -184,13 +185,13 @@ def _kalman_semi_implicit(Z_next, P_x_k_k, A_1, A_2, b, H, C,
184185
Measurement noise covariance
185186
"""
186187
I = np.eye(A_1.shape[0])
187-
y_k1_k = b
188188
A_1_inv = np.linalg.inv(A_1)
189-
H_1 = H @ A_1_inv
190-
P_y_k1_k = A_2 @ P_x_k_k @ A_2.T + C @ Qcov @ C.T
191-
L_y_k1 = P_y_k1_k @ H_1.T @ np.linalg.inv((H_1 @ P_y_k1_k @ H_1.T) + Rcov)
192-
P_y_k1_k1 = (I - L_y_k1 @ H_1) @ P_y_k1_k
193-
b_hat = y_k1_k + L_y_k1 @ (Z_next - H_1 @ y_k1_k)
194-
P_x_k1_k1 = A_1_inv @ P_y_k1_k1 @ A_1_inv.T
195-
return b_hat, P_x_k1_k1
196-
189+
190+
x_k1_k = A_1_inv @ b
191+
P_x_k1_k = A_1_inv @ A_2 @ P_x_k_k @ A_2.T @ A_1_inv.T + C @ Qcov @ C.T
192+
L_x_k1 = P_x_k1_k @ H.T @ np.linalg.inv((H @ P_x_k1_k @ H.T) + Rcov)
193+
P_zz = (H @ P_x_k1_k @ H.T) + Rcov
194+
P_x_k1_k1 = (I - L_x_k1 @ H) @ P_x_k1_k
195+
x_hat = x_k1_k + L_x_k1 @ (Z_next - H @ x_k1_k)
196+
b_hat = A_1 @ x_hat
197+
return b_hat, P_x_k1_k1, P_zz

pipedream_solver/simulation.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
except:
1212
_HAS_NUMBA = False
1313
if _HAS_NUMBA:
14-
from pipedream_solver.nutils import interpolate_sample, _kalman_semi_implicit
14+
from pipedream_solver.nutils import interpolate_sample, _kalman_semi_implicit, _square_root_kalman_semi_implicit
1515
else:
16-
from pipedream_solver.utils import interpolate_sample, _kalman_semi_implicit
16+
from pipedream_solver.utils import interpolate_sample, _kalman_semi_implicit, _square_root_kalman_semi_implicit
1717

1818
eps = np.finfo(float).eps
1919

@@ -86,7 +86,7 @@ class Simulation():
8686
def __init__(self, model, Q_in=None, H_bc=None, Q_Ik=None, t_start=None,
8787
t_end=None, dt=None, max_iter=None, min_dt=1, max_dt=200,
8888
tol=0.01, min_rel_change=1e-10, max_rel_change=1e10, safety_factor=0.9,
89-
Qcov=None, Rcov=None, C=None, H=None, interpolation_method='linear'):
89+
Pxx = None, Qcov=None, Rcov=None, C=None, H=None, interpolation_method='linear'):
9090
self.model = model
9191
if Q_in is not None:
9292
self.Q_in = Q_in.copy(deep=True)
@@ -204,7 +204,12 @@ def __init__(self, model, Q_in=None, H_bc=None, Q_Ik=None, t_start=None,
204204
else:
205205
assert isinstance(H, np.ndarray)
206206
self.H = H
207-
self.P_x_k_k = self.C @ self.Qcov @ self.C.T
207+
if Pxx is None:
208+
self.P_x_k_k = self.C @ self.Qcov @ self.C.T
209+
else:
210+
self.P_x_k_k = Pxx.copy()
211+
self.A_1 = None
212+
self.P_zz = None
208213
# Progress bar checkpoints
209214
if np.isfinite(self.t_end):
210215
self._checkpoints = np.linspace(self.t_start, self.t_end)
@@ -447,7 +452,7 @@ def filter_step_size(self, tol=0.5, dts=None, errs=None, coeffs=[0.5, 0.5, 0, 0.
447452
return dt_np1
448453

449454
def kalman_filter(self, Z, H=None, C=None, Qcov=None, Rcov=None, P_x_k_k=None,
450-
dt=None, **kwargs):
455+
dt=None, SR=False, **kwargs):
451456
"""
452457
Apply Kalman Filter to fuse observed data into model.
453458
@@ -481,9 +486,15 @@ def kalman_filter(self, Z, H=None, C=None, Qcov=None, Rcov=None, P_x_k_k=None,
481486
if Rcov is None:
482487
Rcov = self.Rcov
483488
A_1, A_2, b = self.model._semi_implicit_system(_dt=dt)
484-
b_hat, P_x_k_k = _kalman_semi_implicit(Z, P_x_k_k, A_1, A_2, b, H, C,
489+
if SR == False:
490+
b_hat, P_x_k_k, P_zz = _kalman_semi_implicit(Z, P_x_k_k, A_1, A_2, b, H, C,
491+
Qcov, Rcov)
492+
else:
493+
b_hat, P_x_k_k, P_zz = _square_root_kalman_semi_implicit(Z, P_x_k_k, A_1, A_2, b, H, C,
485494
Qcov, Rcov)
486495
self.P_x_k_k = P_x_k_k
496+
self.P_zz = P_zz
497+
self.A_1 = A_1
487498
self.model.b = b_hat
488499
self.model.iter_count -= 1
489500
self.model.t -= dt

pipedream_solver/utils.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,58 @@ def _kalman_semi_implicit(Z_next, P_x_k_k, A_1, A_2, b, H, C,
124124
Measurement noise covariance
125125
"""
126126
I = np.eye(A_1.shape[0])
127-
y_k1_k = b
128127
A_1_inv = np.linalg.inv(A_1)
129-
H_1 = H @ A_1_inv
130-
P_y_k1_k = A_2 @ P_x_k_k @ A_2.T + C @ Qcov @ C.T
131-
L_y_k1 = P_y_k1_k @ H_1.T @ np.linalg.inv((H_1 @ P_y_k1_k @ H_1.T) + Rcov)
132-
P_y_k1_k1 = (I - L_y_k1 @ H_1) @ P_y_k1_k
133-
b_hat = y_k1_k + L_y_k1 @ (Z_next - H_1 @ y_k1_k)
134-
P_x_k1_k1 = A_1_inv @ P_y_k1_k1 @ A_1_inv.T
135-
return b_hat, P_x_k1_k1
128+
129+
x_k1_k = A_1_inv @ b
130+
P_x_k1_k = A_1_inv @ A_2 @ P_x_k_k @ A_2.T @ A_1_inv.T + C @ Qcov @ C.T
131+
L_x_k1 = P_x_k1_k @ H.T @ np.linalg.inv((H @ P_x_k1_k @ H.T) + Rcov)
132+
P_zz = (H @ P_x_k1_k @ H.T) + Rcov
133+
P_x_k1_k1 = (I - L_x_k1 @ H) @ P_x_k1_k
134+
x_hat = x_k1_k + L_x_k1 @ (Z_next - H @ x_k1_k)
135+
b_hat = A_1 @ x_hat
136+
return b_hat, P_x_k1_k1, P_zz
136137

138+
def _square_root_kalman_semi_implicit(Z_next, P_x_k_k, A_1, A_2, b, H, C,
139+
Qcov, Rcov):
140+
"""
141+
Perform Kalman filtering to estimate state and error covariance.
142+
Inputs:
143+
-------
144+
Z_next : np.ndarray (b x 1)
145+
Observed data
146+
P_x_k_k : np.ndarray (M x M)
147+
Posterior error covariance estimate at previous timestep
148+
A_1 : np.ndarray (M x M)
149+
Left state transition matrix
150+
A_2 : np.ndarray (M x M)
151+
Right state transition matrix
152+
b : np.ndarray (M x 1)
153+
Right-hand side solution vector
154+
H : np.ndarray (M x b)
155+
Observation matrix
156+
C : np.ndarray (a x M)
157+
Signal-input matrix
158+
Qcov : np.ndarray (M x M)
159+
Process noise covariance
160+
Rcov : np.ndarray (M x M)
161+
Measurement noise covariance
162+
"""
163+
I = np.eye(A_1.shape[0])
164+
A_1_inv = np.linalg.inv(A_1)
165+
Rq = np.linalg.cholesky(Qcov)
166+
Rr = np.linalg.cholesky(Rcov)
167+
F = np.linalg.cholesky(P_x_k_k)
168+
169+
x_k1_k = A_1_inv @ b
170+
Fbar = np.linalg.qr(np.vstack((F@A_2.T@A_1_inv.T, Rq)), mode='r')
171+
172+
G = np.linalg.qr(np.block([[Fbar@H.T], [Rr]]), mode='r')
173+
174+
L_x_k1 = (np.linalg.inv(G)@(np.linalg.inv(G).T@H)@Fbar.T@Fbar).T
175+
176+
Fhat = np.linalg.qr(np.block([[Fbar@(I - L_x_k1@H).T], [Rr@L_x_k1.T]]), mode='r')
177+
x_hat = x_k1_k + L_x_k1 @ (Z_next - H @ x_k1_k)
178+
179+
P_x_k1_k1 = Fhat.T@Fhat
180+
b_hat = A_1 @ x_hat
181+
return b_hat, P_x_k1_k1

0 commit comments

Comments
 (0)