dfm-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dfm_python/__init__.py +28 -0
- dfm_python/config.py +435 -0
- dfm_python/data_loader.py +500 -0
- dfm_python/dfm.py +2356 -0
- dfm_python/kalman.py +403 -0
- dfm_python/news.py +557 -0
- dfm_python-0.1.0.dist-info/METADATA +271 -0
- dfm_python-0.1.0.dist-info/RECORD +9 -0
- dfm_python-0.1.0.dist-info/WHEEL +4 -0
dfm_python/kalman.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Kalman filter and fixed-interval smoother implementation."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.linalg import inv, pinv
|
|
5
|
+
from typing import Tuple, Dict
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
# Set up logger (optional - only if logging is enabled)
|
|
10
|
+
_logger = logging.getLogger(__name__)
|
|
11
|
+
if not _logger.handlers:
|
|
12
|
+
# Only configure if logging is enabled (avoid overhead if disabled)
|
|
13
|
+
root_logger = logging.getLogger()
|
|
14
|
+
if root_logger.level <= logging.WARNING:
|
|
15
|
+
_logger.setLevel(logging.WARNING)
|
|
16
|
+
handler = logging.StreamHandler()
|
|
17
|
+
handler.setFormatter(logging.Formatter(
|
|
18
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
19
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
20
|
+
))
|
|
21
|
+
_logger.addHandler(handler)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class KalmanFilterState:
|
|
26
|
+
"""Kalman filter state structure."""
|
|
27
|
+
Zm: np.ndarray # Prior/predicted factor state (m x nobs)
|
|
28
|
+
Vm: np.ndarray # Prior covariance (m x m x nobs)
|
|
29
|
+
ZmU: np.ndarray # Posterior/updated state (m x (nobs+1))
|
|
30
|
+
VmU: np.ndarray # Posterior covariance (m x m x (nobs+1))
|
|
31
|
+
loglik: float # Log-likelihood
|
|
32
|
+
k_t: np.ndarray # Kalman gain
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def miss_data(y: np.ndarray, C: np.ndarray, R: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
36
|
+
"""Eliminate rows in y, C, R corresponding to missing data (NaN) in y.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
-----------
|
|
40
|
+
y : np.ndarray
|
|
41
|
+
Vector of observations at time t (k,)
|
|
42
|
+
C : np.ndarray
|
|
43
|
+
Observation matrix (k x m)
|
|
44
|
+
R : np.ndarray
|
|
45
|
+
Covariance for observation matrix residuals (k x k)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
--------
|
|
49
|
+
y : np.ndarray
|
|
50
|
+
Reduced observation vector
|
|
51
|
+
C : np.ndarray
|
|
52
|
+
Reduced observation matrix
|
|
53
|
+
R : np.ndarray
|
|
54
|
+
Reduced covariance matrix
|
|
55
|
+
L : np.ndarray
|
|
56
|
+
Selection matrix to restore standard dimensions
|
|
57
|
+
"""
|
|
58
|
+
# Returns True for nonmissing series
|
|
59
|
+
ix = ~np.isnan(y)
|
|
60
|
+
|
|
61
|
+
# Index for columns with nonmissing variables
|
|
62
|
+
e = np.eye(len(y))
|
|
63
|
+
L = e[:, ix]
|
|
64
|
+
|
|
65
|
+
# Remove missing series
|
|
66
|
+
y = y[ix]
|
|
67
|
+
|
|
68
|
+
# Remove missing series from observation matrix
|
|
69
|
+
C = C[ix, :]
|
|
70
|
+
|
|
71
|
+
# Remove missing series from covariance matrix
|
|
72
|
+
R = R[np.ix_(ix, ix)]
|
|
73
|
+
|
|
74
|
+
return y, C, R, L
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def skf(Y: np.ndarray, A: np.ndarray, C: np.ndarray, Q: np.ndarray,
|
|
78
|
+
R: np.ndarray, Z_0: np.ndarray, V_0: np.ndarray) -> KalmanFilterState:
|
|
79
|
+
"""Apply Kalman filter (forward pass).
|
|
80
|
+
|
|
81
|
+
Parameters:
|
|
82
|
+
-----------
|
|
83
|
+
Y : np.ndarray
|
|
84
|
+
Input data (k x nobs), where k = number of series, nobs = time periods
|
|
85
|
+
A : np.ndarray
|
|
86
|
+
Transition matrix (m x m)
|
|
87
|
+
C : np.ndarray
|
|
88
|
+
Observation matrix (k x m)
|
|
89
|
+
Q : np.ndarray
|
|
90
|
+
Covariance for transition equation residuals (m x m)
|
|
91
|
+
R : np.ndarray
|
|
92
|
+
Covariance for observation matrix residuals (k x k)
|
|
93
|
+
Z_0 : np.ndarray
|
|
94
|
+
Initial state vector (m,)
|
|
95
|
+
V_0 : np.ndarray
|
|
96
|
+
Initial state covariance matrix (m x m)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
--------
|
|
100
|
+
KalmanFilterState
|
|
101
|
+
Filter state with prior and posterior estimates
|
|
102
|
+
"""
|
|
103
|
+
# Dimensions
|
|
104
|
+
k, nobs = Y.shape # k series, nobs time periods
|
|
105
|
+
m = C.shape[1] # m factors
|
|
106
|
+
|
|
107
|
+
# Initialize output
|
|
108
|
+
Zm = np.full((m, nobs), np.nan) # Z_t | t-1 (prior)
|
|
109
|
+
Vm = np.full((m, m, nobs), np.nan) # V_t | t-1 (prior)
|
|
110
|
+
ZmU = np.full((m, nobs + 1), np.nan) # Z_t | t (posterior/updated)
|
|
111
|
+
VmU = np.full((m, m, nobs + 1), np.nan) # V_t | t (posterior/updated)
|
|
112
|
+
loglik = 0.0
|
|
113
|
+
|
|
114
|
+
# Set initial values
|
|
115
|
+
Zu = Z_0.copy() # Z_0|0 (In loop, Zu gives Z_t | t)
|
|
116
|
+
Vu = V_0.copy() # V_0|0 (In loop, Vu gives V_t | t)
|
|
117
|
+
|
|
118
|
+
# Store initial values
|
|
119
|
+
ZmU[:, 0] = Zu
|
|
120
|
+
VmU[:, :, 0] = Vu
|
|
121
|
+
|
|
122
|
+
# Kalman filter procedure
|
|
123
|
+
for t in range(nobs):
|
|
124
|
+
# Calculate prior distribution
|
|
125
|
+
# Use transition equation to create prior estimate for factor
|
|
126
|
+
# i.e. Z = Z_t|t-1
|
|
127
|
+
# Check for NaN/Inf in inputs
|
|
128
|
+
if np.any(np.isnan(Zu)) or np.any(np.isinf(Zu)):
|
|
129
|
+
_logger.warning(f"skf: Zu contains NaN/Inf at t={t}, resetting to zeros")
|
|
130
|
+
Zu = np.zeros_like(Zu)
|
|
131
|
+
|
|
132
|
+
Z = A @ Zu
|
|
133
|
+
|
|
134
|
+
# Check for NaN/Inf in Z
|
|
135
|
+
if np.any(np.isnan(Z)) or np.any(np.isinf(Z)):
|
|
136
|
+
_logger.warning(f"skf: Z contains NaN/Inf at t={t}, using previous Zu")
|
|
137
|
+
Z = Zu.copy()
|
|
138
|
+
|
|
139
|
+
# Prior covariance matrix of Z (i.e. V = V_t|t-1)
|
|
140
|
+
# Var(Z) = Var(A*Z + u_t) = Var(A*Z) + Var(u) = A*Vu*A' + Q
|
|
141
|
+
V = A @ Vu @ A.T + Q
|
|
142
|
+
V = 0.5 * (V + V.T) # Trick to make symmetric
|
|
143
|
+
|
|
144
|
+
# Numerical stability: ensure V is positive semi-definite
|
|
145
|
+
# Add small regularization if needed
|
|
146
|
+
try:
|
|
147
|
+
eigenvals = np.linalg.eigvals(V)
|
|
148
|
+
min_eigenval = np.min(eigenvals)
|
|
149
|
+
if min_eigenval < 1e-8:
|
|
150
|
+
# Add regularization to ensure positive semi-definiteness
|
|
151
|
+
V = V + np.eye(V.shape[0]) * (1e-8 - min_eigenval)
|
|
152
|
+
V = 0.5 * (V + V.T) # Re-symmetrize
|
|
153
|
+
except (np.linalg.LinAlgError, ValueError):
|
|
154
|
+
# Eigendecomposition failed - add small regularization
|
|
155
|
+
V = V + np.eye(V.shape[0]) * 1e-8
|
|
156
|
+
V = 0.5 * (V + V.T)
|
|
157
|
+
|
|
158
|
+
# Check for NaN/Inf
|
|
159
|
+
if np.any(np.isnan(V)) or np.any(np.isinf(V)):
|
|
160
|
+
# Fallback: use previous covariance with regularization
|
|
161
|
+
V = Vu + np.eye(V.shape[0]) * 1e-6
|
|
162
|
+
V = 0.5 * (V + V.T)
|
|
163
|
+
|
|
164
|
+
# Calculate posterior distribution
|
|
165
|
+
# Remove missing series: These are removed from Y, C, and R
|
|
166
|
+
Y_t, C_t, R_t, _ = miss_data(Y[:, t], C, R)
|
|
167
|
+
|
|
168
|
+
# Check if y_t contains no data
|
|
169
|
+
if len(Y_t) == 0:
|
|
170
|
+
Zu = Z
|
|
171
|
+
Vu = V
|
|
172
|
+
else:
|
|
173
|
+
# Steps for variance and population regression coefficients:
|
|
174
|
+
# Var(c_t*Z_t + e_t) = c_t Var(Z) c_t' + Var(e) = c_t*V*c_t' + R
|
|
175
|
+
VC = V @ C_t.T
|
|
176
|
+
|
|
177
|
+
# Compute innovation covariance F = C_t @ V @ C_t.T + R_t
|
|
178
|
+
F = C_t @ VC + R_t
|
|
179
|
+
F = 0.5 * (F + F.T) # Ensure symmetry
|
|
180
|
+
|
|
181
|
+
# Numerical stability: regularize F if needed
|
|
182
|
+
try:
|
|
183
|
+
F_eigenvals = np.linalg.eigvals(F)
|
|
184
|
+
min_F_eigenval = np.min(F_eigenvals)
|
|
185
|
+
if min_F_eigenval < 1e-8:
|
|
186
|
+
F = F + np.eye(F.shape[0]) * (1e-8 - min_F_eigenval)
|
|
187
|
+
F = 0.5 * (F + F.T)
|
|
188
|
+
except (np.linalg.LinAlgError, ValueError):
|
|
189
|
+
F = F + np.eye(F.shape[0]) * 1e-8
|
|
190
|
+
F = 0.5 * (F + F.T)
|
|
191
|
+
|
|
192
|
+
# Check for NaN/Inf before inversion
|
|
193
|
+
if np.any(np.isnan(F)) or np.any(np.isinf(F)):
|
|
194
|
+
# Fallback: use identity with large variance
|
|
195
|
+
F = np.eye(F.shape[0]) * 1e6
|
|
196
|
+
_logger.warning(f"skf: F matrix contains NaN/Inf at t={t}, using fallback")
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
iF = inv(F)
|
|
200
|
+
except (np.linalg.LinAlgError, ValueError) as e:
|
|
201
|
+
# Matrix inversion failed - use pseudo-inverse with regularization
|
|
202
|
+
F_reg = F + np.eye(F.shape[0]) * 1e-6
|
|
203
|
+
iF = pinv(F_reg)
|
|
204
|
+
_logger.warning(f"skf: F inversion failed at t={t}, using pinv: {type(e).__name__}")
|
|
205
|
+
|
|
206
|
+
# Matrix of population regression coefficients (Kalman gain)
|
|
207
|
+
VCF = VC @ iF
|
|
208
|
+
|
|
209
|
+
# Difference between actual and predicted observation matrix values
|
|
210
|
+
innov = Y_t - C_t @ Z
|
|
211
|
+
|
|
212
|
+
# Check for NaN/Inf in innovation
|
|
213
|
+
if np.any(np.isnan(innov)) or np.any(np.isinf(innov)):
|
|
214
|
+
_logger.warning(f"skf: Innovation contains NaN/Inf at t={t}, skipping update")
|
|
215
|
+
Zu = Z
|
|
216
|
+
Vu = V
|
|
217
|
+
else:
|
|
218
|
+
# Update estimate of factor values (posterior)
|
|
219
|
+
Zu = Z + VCF @ innov
|
|
220
|
+
|
|
221
|
+
# Clean NaN/Inf only (remove excessive clipping during iterations)
|
|
222
|
+
if np.any(~np.isfinite(Zu)):
|
|
223
|
+
Zu = np.nan_to_num(Zu, nan=0.0, posinf=0.0, neginf=0.0)
|
|
224
|
+
|
|
225
|
+
# Update covariance matrix (posterior) for time t
|
|
226
|
+
Vu = V - VCF @ VC.T
|
|
227
|
+
Vu = 0.5 * (Vu + Vu.T) # Approximation trick to make symmetric
|
|
228
|
+
|
|
229
|
+
# Clean NaN/Inf only (keep regularization for PSD)
|
|
230
|
+
if np.any(~np.isfinite(Vu)):
|
|
231
|
+
Vu = np.nan_to_num(Vu, nan=1e-8, posinf=1e6, neginf=1e-8)
|
|
232
|
+
|
|
233
|
+
# Ensure Vu is positive semi-definite
|
|
234
|
+
try:
|
|
235
|
+
Vu_eigenvals = np.linalg.eigvals(Vu)
|
|
236
|
+
min_Vu_eigenval = np.min(Vu_eigenvals)
|
|
237
|
+
if min_Vu_eigenval < 1e-8:
|
|
238
|
+
Vu = Vu + np.eye(Vu.shape[0]) * (1e-8 - min_Vu_eigenval)
|
|
239
|
+
Vu = 0.5 * (Vu + Vu.T)
|
|
240
|
+
except (np.linalg.LinAlgError, ValueError):
|
|
241
|
+
Vu = Vu + np.eye(Vu.shape[0]) * 1e-8
|
|
242
|
+
Vu = 0.5 * (Vu + Vu.T)
|
|
243
|
+
|
|
244
|
+
# Check for NaN/Inf in Vu
|
|
245
|
+
if np.any(np.isnan(Vu)) or np.any(np.isinf(Vu)):
|
|
246
|
+
_logger.warning(f"skf: Vu contains NaN/Inf at t={t}, using V as fallback")
|
|
247
|
+
Vu = V.copy()
|
|
248
|
+
|
|
249
|
+
# Update log-likelihood (with safeguards)
|
|
250
|
+
try:
|
|
251
|
+
det_iF = np.linalg.det(iF)
|
|
252
|
+
if det_iF > 0 and np.isfinite(det_iF):
|
|
253
|
+
log_det = np.log(det_iF)
|
|
254
|
+
innov_term = innov.T @ iF @ innov
|
|
255
|
+
if np.isfinite(innov_term):
|
|
256
|
+
loglik += 0.5 * (log_det - innov_term)
|
|
257
|
+
else:
|
|
258
|
+
_logger.debug(f"skf: innov_term not finite at t={t}, skipping loglik update")
|
|
259
|
+
else:
|
|
260
|
+
_logger.debug(f"skf: det(iF) <= 0 or not finite at t={t}, skipping loglik update")
|
|
261
|
+
except (np.linalg.LinAlgError, ValueError, OverflowError):
|
|
262
|
+
_logger.debug(f"skf: Log-likelihood calculation failed at t={t}")
|
|
263
|
+
|
|
264
|
+
# Store output
|
|
265
|
+
# Store covariance and observation values for t (priors)
|
|
266
|
+
Zm[:, t] = Z
|
|
267
|
+
Vm[:, :, t] = V
|
|
268
|
+
|
|
269
|
+
# Store covariance and state values for t (posteriors)
|
|
270
|
+
# i.e. Zu = Z_t|t & Vu = V_t|t
|
|
271
|
+
ZmU[:, t + 1] = Zu
|
|
272
|
+
VmU[:, :, t + 1] = Vu
|
|
273
|
+
|
|
274
|
+
# Store Kalman gain k_t (from final iteration)
|
|
275
|
+
if len(Y_t) == 0:
|
|
276
|
+
k_t = np.zeros((m, m))
|
|
277
|
+
else:
|
|
278
|
+
k_t = VCF @ C_t
|
|
279
|
+
|
|
280
|
+
return KalmanFilterState(Zm=Zm, Vm=Vm, ZmU=ZmU, VmU=VmU, loglik=loglik, k_t=k_t)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def fis(A: np.ndarray, S: KalmanFilterState) -> KalmanFilterState:
|
|
284
|
+
"""Apply fixed-interval smoother (backward pass).
|
|
285
|
+
|
|
286
|
+
Parameters:
|
|
287
|
+
-----------
|
|
288
|
+
A : np.ndarray
|
|
289
|
+
Transition matrix (m x m)
|
|
290
|
+
S : KalmanFilterState
|
|
291
|
+
State from Kalman filter (SKF)
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
--------
|
|
295
|
+
KalmanFilterState
|
|
296
|
+
State with smoothed estimates added (ZmT, VmT, VmT_1)
|
|
297
|
+
"""
|
|
298
|
+
m, nobs = S.Zm.shape
|
|
299
|
+
|
|
300
|
+
# Initialize output matrices
|
|
301
|
+
ZmT = np.zeros((m, nobs + 1))
|
|
302
|
+
VmT = np.zeros((m, m, nobs + 1))
|
|
303
|
+
|
|
304
|
+
# Fill the final period of ZmT, VmT with SKF posterior values
|
|
305
|
+
ZmT[:, nobs] = S.ZmU[:, nobs]
|
|
306
|
+
VmT[:, :, nobs] = S.VmU[:, :, nobs]
|
|
307
|
+
|
|
308
|
+
# Initialize VmT_1 lag 1 covariance matrix for final period
|
|
309
|
+
VmT_1 = np.zeros((m, m, nobs))
|
|
310
|
+
VmT_1[:, :, nobs - 1] = (np.eye(m) - S.k_t) @ A @ S.VmU[:, :, nobs - 1]
|
|
311
|
+
|
|
312
|
+
# Used for recursion process
|
|
313
|
+
J_2 = S.VmU[:, :, nobs - 1] @ A.T @ pinv(S.Vm[:, :, nobs - 1])
|
|
314
|
+
|
|
315
|
+
# Run smoothing algorithm
|
|
316
|
+
# Loop through time reverse-chronologically (starting at final period nobs-1)
|
|
317
|
+
for t in range(nobs - 1, -1, -1):
|
|
318
|
+
# Store posterior and prior factor covariance values
|
|
319
|
+
VmU = S.VmU[:, :, t]
|
|
320
|
+
Vm1 = S.Vm[:, :, t]
|
|
321
|
+
|
|
322
|
+
# Store previous period smoothed factor covariance and lag-1 covariance
|
|
323
|
+
V_T = VmT[:, :, t + 1]
|
|
324
|
+
V_T1 = VmT_1[:, :, t] if t < nobs - 1 else np.zeros((m, m))
|
|
325
|
+
|
|
326
|
+
J_1 = J_2
|
|
327
|
+
|
|
328
|
+
# Update smoothed factor estimate
|
|
329
|
+
ZmT[:, t] = S.ZmU[:, t] + J_1 @ (ZmT[:, t + 1] - A @ S.ZmU[:, t])
|
|
330
|
+
|
|
331
|
+
# Clean NaN/Inf only (remove excessive clipping)
|
|
332
|
+
if np.any(~np.isfinite(ZmT[:, t])):
|
|
333
|
+
ZmT[:, t] = np.nan_to_num(ZmT[:, t], nan=0.0, posinf=0.0, neginf=0.0)
|
|
334
|
+
|
|
335
|
+
# Update smoothed factor covariance matrix
|
|
336
|
+
VmT[:, :, t] = VmU + J_1 @ (V_T - Vm1) @ J_1.T
|
|
337
|
+
VmT[:, :, t] = 0.5 * (VmT[:, :, t] + VmT[:, :, t].T) # Ensure symmetry
|
|
338
|
+
|
|
339
|
+
# Clean NaN/Inf and ensure PSD (keep only critical regularization)
|
|
340
|
+
if np.any(~np.isfinite(VmT[:, :, t])):
|
|
341
|
+
VmT[:, :, t] = np.nan_to_num(VmT[:, :, t], nan=1e-8, posinf=1e6, neginf=1e-8)
|
|
342
|
+
|
|
343
|
+
if t > 0:
|
|
344
|
+
# Update weight
|
|
345
|
+
J_2 = S.VmU[:, :, t - 1] @ A.T @ pinv(S.Vm[:, :, t - 1])
|
|
346
|
+
|
|
347
|
+
# Update lag 1 factor covariance matrix
|
|
348
|
+
VmT_1[:, :, t - 1] = VmU @ J_2.T + J_1 @ (V_T1 - A @ VmU) @ J_2.T
|
|
349
|
+
|
|
350
|
+
# Add smoothed estimates as attributes
|
|
351
|
+
S.ZmT = ZmT
|
|
352
|
+
S.VmT = VmT
|
|
353
|
+
S.VmT_1 = VmT_1
|
|
354
|
+
|
|
355
|
+
return S
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def run_kf(Y: np.ndarray, A: np.ndarray, C: np.ndarray, Q: np.ndarray,
|
|
359
|
+
R: np.ndarray, Z_0: np.ndarray, V_0: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray, float]:
|
|
360
|
+
"""Apply Kalman filter and fixed-interval smoother.
|
|
361
|
+
|
|
362
|
+
Parameters:
|
|
363
|
+
-----------
|
|
364
|
+
Y : np.ndarray
|
|
365
|
+
Input data (k x nobs)
|
|
366
|
+
A : np.ndarray
|
|
367
|
+
Transition matrix (m x m)
|
|
368
|
+
C : np.ndarray
|
|
369
|
+
Observation matrix (k x m)
|
|
370
|
+
Q : np.ndarray
|
|
371
|
+
Covariance for transition residuals (m x m)
|
|
372
|
+
R : np.ndarray
|
|
373
|
+
Covariance for observation residuals (k x k)
|
|
374
|
+
Z_0 : np.ndarray
|
|
375
|
+
Initial state (m,)
|
|
376
|
+
V_0 : np.ndarray
|
|
377
|
+
Initial covariance (m x m)
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
--------
|
|
381
|
+
zsmooth : np.ndarray
|
|
382
|
+
Smoothed factor estimates (m x (nobs+1)), zsmooth[:, t+1] = Z_t|T
|
|
383
|
+
Vsmooth : np.ndarray
|
|
384
|
+
Smoothed factor covariance (m x m x (nobs+1)), Vsmooth[:, :, t+1] = Cov(Z_t|T)
|
|
385
|
+
VVsmooth : np.ndarray
|
|
386
|
+
Lag 1 factor covariance (m x m x nobs), Cov(Z_t, Z_t-1|T)
|
|
387
|
+
loglik : float
|
|
388
|
+
Log-likelihood
|
|
389
|
+
"""
|
|
390
|
+
# Kalman filter
|
|
391
|
+
S = skf(Y, A, C, Q, R, Z_0, V_0)
|
|
392
|
+
|
|
393
|
+
# Fixed-interval smoother
|
|
394
|
+
S = fis(A, S)
|
|
395
|
+
|
|
396
|
+
# Organize output
|
|
397
|
+
zsmooth = S.ZmT
|
|
398
|
+
Vsmooth = S.VmT
|
|
399
|
+
VVsmooth = S.VmT_1
|
|
400
|
+
loglik = S.loglik
|
|
401
|
+
|
|
402
|
+
return zsmooth, Vsmooth, VVsmooth, loglik
|
|
403
|
+
|