code: def filter_step(G, covY, pred, yt):
      
      # 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
docs: 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}