Filtering step of Kalman filter.

    Parameters
    ----------
    G:  (dy, dx) numpy array
        mean of Y_t | X_t is G * X_t
    covX: (dx, dx) numpy array 
        covariance of Y_t | X_t
    pred: MeanAndCov object
        predictive distribution at time t

    Returns
    -------
    pred: MeanAndCov object
        filtering distribution at time t
    logpyt: float
        log density of Y_t | Y_{0:t-1}

def filter_step(G, covY, pred, yt):
    """Filtering step of Kalman filter.

    Parameters
    ----------
    G:  (dy, dx) numpy array
        mean of Y_t | X_t is G * X_t
    covX: (dx, dx) numpy array 
        covariance of Y_t | X_t
    pred: MeanAndCov object
        predictive distribution at time t

    Returns
    -------
    pred: MeanAndCov object
        filtering distribution at time t
    logpyt: float
        log density of Y_t | Y_{0:t-1}
    """
    # data prediction
    data_pred_mean = np.matmul(pred.mean, G.T)
    data_pred_cov = dotdot(G, pred.cov, G.T) + covY
    if covY.shape[0] == 1:
        logpyt = dists.Normal(loc=data_pred_mean,
                              scale=np.sqrt(data_pred_cov)).logpdf(yt)
    else:
        logpyt = dists.MvNormal(loc=data_pred_mean,
                                cov=data_pred_cov).logpdf(yt)
    # filter
    residual = yt - data_pred_mean
    gain = dotdot(pred.cov, G.T, inv(data_pred_cov))
    filt_mean = pred.mean + np.matmul(residual, gain.T)
    filt_cov = pred.cov - dotdot(gain, G, pred.cov)
    return MeanAndCov(mean=filt_mean, cov=filt_cov), logpyt