def _filter_xs(self, s_0, s_ts, pi_0, sigma_0, As, Cs, Qs, Rs): """ Computes x_1|1, P_1|1, ..., x_T|T, P_T|T given observations, parameters and values of switching variable at each time. :returns: -n_LF x T numpy array Smoothed means -T x n_LF x n_LF numpy array Smoothed covariances -T-1 x n_LF x n_LF numpy array Smoothed lagged covariances (ie, cov[x_t, x_t-1]) """ # Initialize Kalman filter using values of parameters at t = 0, even though they're never used kf = Kalman(mu_0=pi_0.copy(), sigma_0=sigma_0.copy(), A=As[s_0], B=None, C=Cs[s_0], D=None, Q=Qs[s_0], R=Rs[s_0]) # x_t|t, P_t|t, t = 1, ..., T x_filts = np.zeros([self.T, self.n_LF]) P_filts = np.zeros([self.T, self.n_LF, self.n_LF]) # x_t|t-1, P_t|t-1. t = 1, ..., T. Indexed by t. x_pred = np.zeros([self.T, self.n_LF]) P_pred = np.zeros([self.T, self.n_LF, self.n_LF]) # Filtering step for t in range(0, self.T): # corresponds to t = 1, ..., T # Change parameters. Never need to use A_{s_0}, etc. kf.A = As[s_ts[t]] kf.C = Cs[s_ts[t]] kf.Q = Qs[s_ts[t]] kf.R = Rs[s_ts[t]] # Compute x_{t|t-1}, P_{t|t-1} kf.predict() x_pred[t] = kf.mu P_pred[t] = kf.sigma # Compute x_{t|t}, P_{t|t} kf.update(self.y_ts[t]) x_filts[t] = kf.mu P_filts[t] = kf.sigma # TODO: run smoother to fill in missing data!!! return x_filts, P_filts, x_pred, P_pred
def kalman_smooth(Y, U, V, pi0, sigma0, A, B, C, D, Q, R, n_LF): """ Runs Kalman filtering and smoothing step of dynamic factor model estimator. Arguments -Y: N x T Observations -U: L x T State controls -V: P x T Observation controls -pi0: n_LF x 1 -sigma0: n_LF x n_LF -A: n_LF x n_LF -B: n_LF x L -C: N x n_LF -D: N x M -Q: n_LF x n_LF -R: N x N Returns -n_LF x T numpy array Smoothed means -T x n_LF x n_LF numpy array Smoothed covariances -T-1 x n_LF x n_LF numpy array Smoothed lagged covariances (ie, cov[x_t, x_t-1]) """ N, T = Y.shape # Initialize Kalman filter kf = Kalman(mu_0=pi0.copy(), sigma_0=sigma0.copy(), A=A, B=B, C=C, D=D, Q=Q, R=R) # sigma_t|t, mu_t|t sigma_filt = np.zeros([T, n_LF, n_LF]) sigma_filt[0] = sigma0.copy() mu_filt = np.zeros([T, n_LF]) mu_filt[0] = pi0.copy() # sigma_t|t-1, mu_t|t-1. NOTE: indexed by t-1!!! sigma_pred = np.zeros([T - 1, n_LF, n_LF]) mu_pred = np.zeros([T - 1, n_LF]) # Filtering step for t in range(1, T): ### Prediction step kf.predict(U[:, t]) # Save mu_t|t-1 and sigma_t|t-1 sigma_pred[t - 1, :, :] = kf.sigma mu_pred[t - 1] = kf.mu ### Update step kf.C = C.copy() kf.D = D.copy() kf.R = R.copy() # Need to change observation, observation control and observation noise matrices if observation is # incomplete! See Shumway and Stoffer page 314. # First, figure out which sensors don't have observations: nan_sensors = np.where(np.isnan(Y[:, t]))[0] # If some observations were present, go through with the update step if nan_sensors.size < N: # Zero those observations y_t = np.nan_to_num(Y[:, t]) # Modify parameters kf.C[nan_sensors, :] = 0.0 kf.D[nan_sensors, :] = 0.0 kf.R[nan_sensors, :] = 0.0 kf.R[:, nan_sensors] = 0.0 kf.R[nan_sensors, nan_sensors] = 1.0 kf.update(y_t, V[:, t]) # Save filtered mean, covariance sigma_filt[t, :, :] = kf.sigma mu_filt[t] = kf.mu # sigma_t|T, mu_t|T sigma_smooth = np.zeros((T, n_LF, n_LF)) mu_smooth = np.zeros((T, n_LF)) # Initialize: sigma_T|T = sigma_T|T(filtered) sigma_smooth[-1] = sigma_filt[-1] # mu_T|T = mu_T|T(filtered) mu_smooth[-1] = mu_filt[-1] # Lagged covariance. Indexed by t-1. sigma_lag_smooth = np.zeros((T - 1, n_LF, n_LF)) # sigmaLag_{T,T-1} = (1 - K_T C) A V_{T-1|T-1}, where K_T is Kalman gain at last timestep. # TODO: unclear what to do here if last observation contains missing components K_T = np.dot(sigma_pred[-1], np.dot(kf.C.T, np.linalg.pinv(np.dot(kf.C, \ np.dot(sigma_pred[-1], kf.C.T)) + kf.R))) sigma_lag_smooth[-1] = np.dot( np.dot((np.identity(n_LF) - np.dot(K_T, kf.C)), kf.A), sigma_filt[-2]) # Backwards Kalman gain J = np.zeros((T - 1, n_LF, n_LF)) # Smoothing step. Runs from t=T-1 to t=0. for t in range(T - 2, -1, -1): # Backward Kalman gain matrix J[t] = np.dot(np.dot(sigma_filt[t], kf.A.T), np.linalg.pinv(sigma_pred[t])) # Smoothed mean mu_smooth[t] = mu_filt[t] + np.dot(J[t], mu_smooth[t + 1] - mu_pred[t]) # Smoothed covariance. This is explicity symmetric. sigma_smooth[t, :, :] = sigma_filt[t] + np.dot( np.dot(J[t], sigma_smooth[t + 1] - sigma_pred[t]), J[t].T) # Lagged smoothed covariance (NOT SYMMETRIC!) for t in range(T - 3, -1, -1): sigma_lag_smooth[t, :, :] = np.dot(sigma_filt[t+1], J[t].T) \ + np.dot(np.dot(J[t+1], (sigma_lag_smooth[t+1] - np.dot(kf.A, sigma_filt[t+1]))), J[t].T) # Fill in missing Y values Y_imp = Y.copy() nanRows, nanCols = np.where(np.isnan(Y_imp)) for s, t in zip(nanRows, nanCols): Y_imp[s, t] = np.dot(C[s, :], mu_smooth[t, :]) + np.dot(D[s, :], V[:, t]) return mu_smooth.T, sigma_smooth, sigma_lag_smooth, Y_imp, sigma_filt