R/statespacer.R

Defines functions statespacer

Documented in statespacer

#' State Space Model Fitting
#'
#' Fits a State Space model as specified by the user.
#'
#' @param y N x p matrix containing the N observations of the p
#'   dependent variables.
#' @param H_format Format of the H system matrix,
#'   the variance - covariance matrix of the observation equation.
#' @param local_level_ind Boolean indicating whether a local level should
#'   be added to the state space model.
#' @param slope_ind Boolean indicating whether a local level + slope should
#'   be added to the state space model.
#' @param BSM_vec Vector containing the BSM seasonalities that have to be added
#'   to the state space model.
#' @param cycle_ind Boolean indicating whether a cycle has to be added
#'   to the state space model.
#' @param addvar_list A list containing the explanatory variables for each of
#'   the dependent variables. The list should contain p (number of dependent
#'   variables) elements. Each element of the list should be a N x k_p matrix,
#'   with k_p being the number of explanatory variables for the pth
#'   dependent variable. If no explanatory variables should be added for one
#'   of the dependent variables, then set the corresponding element to `NULL`.
#' @param level_addvar_list A list containing the explanatory variables for
#'   each of the dependent variables. The list should contain p (number of
#'   dependent variables) elements. Each element of the list should be a
#'   N x k_p matrix, with k_p being the number of explanatory variables
#'   for the pth dependent variable. If no explanatory variables should be
#'   added for one of the dependent variables, then set the corresponding
#'   element to `NULL`.
#' @param arima_list Specifications of the ARIMA components, should be a list
#'   containing vectors of length 3 with the following format: `c(AR, I, MA)`.
#'   Should be a list to allow different ARIMA models for different sets of
#'   dependent variables. Note: The AR and MA coefficients are
#'   constrained such that the AR component is stationary, and the MA
#'   component is invertible.
#'   See \insertCite{ansley1986note;textual}{statespacer} for details about
#'   the transformation used.
#' @param sarima_list Specifications of the SARIMA components, should be a list
#'   containing lists that contain 4 named vectors. Vectors should be named:
#'   "s", "ar", "i", "ma". Should be a list of lists to allow different SARIMA
#'   models for different sets of dependent variables. Note: The AR and MA
#'   coefficients are constrained such that the AR components are stationary,
#'   and the MA components are invertible.
#'   See \insertCite{ansley1986note;textual}{statespacer} for details about
#'   the transformation used. Note: For multivariate models, the order of "s"
#'   matters, as matrix multiplication is not commutative!
#' @param self_spec_list A list containing the specification of the self
#'   specified component. See the Details section for extensive details about
#'   the format that must be followed for this argument.
#' @param exclude_level Vector containing the dependent variables that should
#'   not get a local level.
#' @param exclude_slope Vector containing the dependent variables that should
#'   not get a slope.
#' @param exclude_BSM_list List of vectors, each vector containing the
#'   dependent variables that should not get the corresponding BSM component.
#' @param exclude_cycle_list The dependent variables that should not get the
#'   corresponding cycle component. Should be a list of vectors to allow
#'   different dependent variables to be excluded for different cycles.
#' @param exclude_arima_list The dependent variables that should not be
#'   involved in the corresponding ARIMA component. Should be a list of
#'   vectors to allow different dependent variables to be excluded for
#'   different ARIMA components.
#' @param exclude_sarima_list The dependent variables that should not be
#'   involved in the corresponding SARIMA component. Should be a list of
#'   vectors to allow different dependent variables to be excluded for
#'   different SARIMA components.
#' @param damping_factor_ind Boolean indicating whether a damping factor should
#'   be included. Must be a vector if multiple cycles are included,
#'   to indicate which cycles should include a damping factor.
#' @param format_level Format of the Q_level system matrix
#'   the variance - covariance matrix of the level state equation.
#' @param format_slope Format of the Q_slope system matrix,
#'   the variance - covariance matrix of the slope state equation.
#' @param format_BSM_list Format of the Q_BSM system matrix,
#'   the variance - covariance matrix of the BSM state equation. Should be a
#'   list to allow different formats for different seasonality periods.
#' @param format_cycle_list Format of the Q_cycle system matrix,
#'   the variance - covariance matrix of the cycle state equation. Should be a
#'   list to allow different formats for different cycles.
#' @param format_addvar Format of the Q_addvar system matrix, the
#'   variance - covariance matrix of the explanatory variables state equation.
#' @param format_level_addvar Format of the Q_level_addvar system matrix, the
#'   variance - covariance matrix of the explanatory variables of the level
#'   state equation.
#' @param fit Boolean indicating whether the model should be fit by an
#'   iterative optimisation procedure. If `FALSE`, the model is only evaluated
#'   at the initial values.
#' @param initial Vector of initial values for the parameter search.
#'   The initial values are recycled or truncated if too few or too many values
#'   have been specified.
#' @param method Method that should be used by the \code{\link[stats]{optim}}
#'   or \code{\link[optimx]{optimr}} function to estimate the parameters. Only
#'   used if `fit = TRUE`.
#' @param control A list of control parameters for the
#'   \code{\link[stats]{optim}} or \code{\link[optimx]{optimr}} function. Only
#'   used if `fit = TRUE`.
#' @param collapse Boolean indicating whether the observation vector should be
#'   collapsed. Should only be set to `TRUE` if the dimensionality of the
#'   observation vector exceeds the dimensionality of the state vector.
#'   If this is the case, computational gains can be achieved by collapsing
#'   the observation vector.
#' @param diagnostics Boolean indicating whether diagnostical tests should be
#'   computed. Defaults to `TRUE`.
#' @param standard_errors Boolean indicating whether standard errors should be
#'   computed. \pkg{numDeriv} must be installed in order to compute the
#'   standard errors! Defaults to `TRUE` if \pkg{numDeriv} is available.
#' @param verbose Boolean indicating whether the progress of the optimisation
#'   procedure should be printed. Only used if `fit = TRUE`.
#'
#' @details
#' To fit the specified State Space model, one occasionally has to pay careful
#' attention to the initial values supplied. See
#' \code{vignette("dictionary", "statespacer")} for details.
#' Initial values should not be too large, as some parameters use the
#' transformation exp(2x) to ensure non-negative values, they should also not
#' be too small as some variances might become relatively too close to 0,
#' relative to the magnitude of y.
#'
#' If a component is specified without a `format`, then the format defaults to a
#' diagonal `format`.
#'
#' `self_spec_list` provides a means to incorporate a self-specified component
#' into the State Space model. This argument can only contain any of the
#' following items, of which some are mandatory:
#' * `H_spec`: Boolean indicating whether the H matrix is self-specified.
#'   Should be `TRUE`, if you want to specify the H matrix yourself.
#' * `state_num` (mandatory): The number of state parameters introduced by the
#'   self-specified component. Must be 0 if only `H` is self-specified.
#' * `param_num`: The number of parameters needed by the self-specified
#'   component. Must be specified and greater than 0 if parameters are needed.
#' * `sys_mat_fun`: A function returning a list of system matrices that are
#'   constructed using the parameters. Must have `param` as an argument. The
#'   items in the list returned should have any of the following names: Z,
#'   Tmat, R, Q, a1, P_star, H. Note: Only the system matrices that depend on
#'   the parameters should be returned by the function!
#' * `sys_mat_input`: A list containing additional arguments to `sys_mat_fun`.
#' * `Z`: The Z system matrix if it does not depend on the parameters.
#' * `Tmat`: The T system matrix if it does not depend on the parameters.
#' * `R`: The R system matrix if it does not depend on the parameters.
#' * `Q`: The Q system matrix if it does not depend on the parameters.
#' * `a1`: The initial guess of the state vector. Must be a matrix
#'   with one column.
#' * `P_inf`: The initial diffuse part of the variance - covariance
#'   matrix of the initial state vector. Must be a matrix.
#' * `P_star`: The initial non-diffuse part of the variance - covariance
#'   matrix of the initial state vector if it does not depend on the
#'   parameters. Must be a matrix.
#' * `H`: The H system matrix if it does not depend on the parameters.
#' * `transform_fun`: Function that returns transformed parameters for which
#'   standard errors have to be computed. Must have `param` as an argument.
#' * `transform_input`: A list containing additional arguments to
#'   `transform_fun.`
#' * `state_only`: The indices of the self specified state that do not play a
#'   role in the observation equations, but only in the state equations. Should
#'   only be used if you want to use `collapse = TRUE` and have some state
#'   parameters that do not play a role in the observation equations. Does not
#'   have to be specified for `collapse = FALSE`.
#'
#' Note: System matrices should only be specified once and need to be
#' specified once! That is, system matrices that are returned by `sys_mat_fun`
#' should not be specified directly, and vice versa. So, system matrices need
#' to be either specified directly, or be returned by `sys_mat_fun`. An
#' exception holds for the case where you \strong{only} want to specify `H`
#' yourself. This will not be checked, so be aware of erroneous output if you
#' do not follow the guidelines of specifying `self_spec_list`. If time-varying
#' system matrices are required, return an array for the time-varying system
#' matrix instead of a matrix.
#'
#' @return
#' A statespacer object containing:
#' * `function_call`: A list containing the input to the function.
#' * `system_matrices`: A list containing the system matrices of
#'   the State Space model.
#' * `predicted`: A list containing the predicted components of
#'   the State Space model.
#' * `filtered`: A list containing the filtered components of
#'   the State Space model.
#' * `smoothed`: A list containing the smoothed components of
#'   the State Space model.
#' * `diagnostics`: A list containing items useful for diagnostical tests.
#' * `optim` (if `fit = TRUE`): A list containing the variables that are returned
#'   by the \code{\link[stats]{optim}} or \code{\link[optimx]{optimr}} function.
#' * `loglik_fun`: Function that returns the loglikelihood of the
#'   specified State Space model, as a function of its parameters.
#' * `standard_errors` (if `standard_errors = TRUE`): A list containing the
#'   standard errors of the parameters of the State Space model.
#'
#' For extensive details about the object returned,
#' see \code{vignette("dictionary", "statespacer")}.
#'
#' @author Dylan Beijers, \email{dylanbeijers@@gmail.com}
#'
#' @references
#' \insertRef{durbin2012time}{statespacer}
#'
#' \insertRef{ansley1986note}{statespacer}
#'
#' @examples
#' # Fits a local level model for the Nile data
#' library(datasets)
#' y <- matrix(Nile)
#' fit <- statespacer(initial = 10, y = y, local_level_ind = TRUE)
#'
#' # Plots the filtered estimates
#' plot(
#'   1871:1970, fit$function_call$y,
#'   type = "p", ylim = c(500, 1400),
#'   xlab = NA, ylab = NA
#' )
#' lines(1871:1970, fit$filtered$level, type = "l")
#' lines(
#'   1871:1970, fit$filtered$level +
#'     1.644854 * sqrt(fit$filtered$P[1, 1, ]),
#'   type = "l", col = "gray"
#' )
#' lines(
#'   1871:1970, fit$filtered$level -
#'     1.644854 * sqrt(fit$filtered$P[1, 1, ]),
#'   type = "l", col = "gray"
#' )
#'
#' # Plots the smoothed estimates
#' plot(
#'   1871:1970, fit$function_call$y,
#'   type = "p", ylim = c(500, 1400),
#'   xlab = NA, ylab = NA
#' )
#' lines(1871:1970, fit$smoothed$level, type = "l")
#' lines(
#'   1871:1970, fit$smoothed$level +
#'     1.644854 * sqrt(fit$smoothed$V[1, 1, ]),
#'   type = "l", col = "gray"
#' )
#' lines(
#'   1871:1970, fit$smoothed$level -
#'     1.644854 * sqrt(fit$smoothed$V[1, 1, ]),
#'   type = "l", col = "gray"
#' )
#' @export
statespacer <- function(y,
                        H_format = NULL,
                        local_level_ind = FALSE,
                        slope_ind = FALSE,
                        BSM_vec = NULL,
                        cycle_ind = FALSE,
                        addvar_list = NULL,
                        level_addvar_list = NULL,
                        arima_list = NULL,
                        sarima_list = NULL,
                        self_spec_list = NULL,
                        exclude_level = NULL,
                        exclude_slope = NULL,
                        exclude_BSM_list = lapply(BSM_vec, function(x) 0),
                        exclude_cycle_list = list(0),
                        exclude_arima_list = lapply(arima_list, function(x) 0),
                        exclude_sarima_list = lapply(sarima_list, function(x) 0),
                        damping_factor_ind = rep(TRUE, length(exclude_cycle_list)),
                        format_level = NULL,
                        format_slope = NULL,
                        format_BSM_list = lapply(BSM_vec, function(x) NULL),
                        format_cycle_list = lapply(exclude_cycle_list, function(x) NULL),
                        format_addvar = NULL,
                        format_level_addvar = NULL,
                        fit = TRUE,
                        initial = 0,
                        method = "BFGS",
                        control = list(),
                        collapse = FALSE,
                        diagnostics = TRUE,
                        standard_errors = NULL,
                        verbose = FALSE) {

  # Check whether standard_errors should be computed
  if (is.null(standard_errors)) {
    if (requireNamespace("numDeriv", quietly = TRUE)) {
      standard_errors <- TRUE
    } else {
      standard_errors <- FALSE
    }
  } else if (standard_errors) {
    if (!requireNamespace("numDeriv", quietly = TRUE)) {
      stop(
        "\"numDeriv\" must be installed if standard errors are required.",
        call. = FALSE
      )
    }
  }

  # Check whether optimr is available
  # Note: Negative loglikelihood will be minimised,
  #       equivalent to maximising loglikelihood
  if (requireNamespace("optimx", quietly = TRUE)) {
    optim_fun <- optimx::optimr
    control$maximize <- FALSE
    control$fnscale <- NULL
  } else {
    optim_fun <- stats::optim
    control$fnscale <- 1
    control$maximize <- NULL
  }

  # control$trace
  if (verbose && is.null(control$trace)) {
    control$trace <- TRUE
  }

  # N = Number of observations
  N <- dim(y)[[1]]

  # p = Number of dependent variables
  p <- dim(y)[[2]]
  p2 <- p

  # Construct the system matrices that do not require any parameters
  sys_mat <- GetSysMat(
    p = p,
    param = NULL,
    update_part = FALSE,
    add_residuals = !collapse,
    H_format = H_format,
    local_level_ind = local_level_ind,
    slope_ind = slope_ind,
    BSM_vec = BSM_vec,
    cycle_ind = cycle_ind,
    addvar_list = addvar_list,
    level_addvar_list = level_addvar_list,
    arima_list = arima_list,
    sarima_list = sarima_list,
    self_spec_list = self_spec_list,
    exclude_level = exclude_level,
    exclude_slope = exclude_slope,
    exclude_BSM_list = exclude_BSM_list,
    exclude_cycle_list = exclude_cycle_list,
    exclude_arima_list = exclude_arima_list,
    exclude_sarima_list = exclude_sarima_list,
    damping_factor_ind = damping_factor_ind,
    format_level = format_level,
    format_slope = format_slope,
    format_BSM_list = format_BSM_list,
    format_cycle_list = format_cycle_list,
    format_addvar = format_addvar,
    format_level_addvar = format_level_addvar
  )

  # Adjust input arguments
  H_format <- sys_mat$function_call$H_format
  local_level_ind <- sys_mat$function_call$local_level_ind
  slope_ind <- sys_mat$function_call$slope_ind
  BSM_vec <- sys_mat$function_call$BSM_vec
  cycle_ind <- sys_mat$function_call$cycle_ind
  addvar_list <- sys_mat$function_call$addvar_list
  level_addvar_list <- sys_mat$function_call$level_addvar_list
  arima_list <- sys_mat$function_call$arima_list
  sarima_list <- sys_mat$function_call$sarima_list
  self_spec_list <- sys_mat$function_call$self_spec_list
  exclude_level <- sys_mat$function_call$exclude_level
  exclude_slope <- sys_mat$function_call$exclude_slope
  exclude_BSM_list <- sys_mat$function_call$exclude_BSM_list
  exclude_cycle_list <- sys_mat$function_call$exclude_cycle_list
  exclude_arima_list <- sys_mat$function_call$exclude_arima_list
  exclude_sarima_list <- sys_mat$function_call$exclude_sarima_list
  damping_factor_ind <- sys_mat$function_call$damping_factor_ind
  format_level <- sys_mat$function_call$format_level
  format_slope <- sys_mat$function_call$format_slope
  format_BSM_list <- sys_mat$function_call$format_BSM_list
  format_cycle_list <- sys_mat$function_call$format_cycle_list
  format_addvar <- sys_mat$function_call$format_addvar
  format_level_addvar <- sys_mat$function_call$format_level_addvar

  # Parameter indices and numbers
  param_indices <- sys_mat$param_indices
  param_num_list <- sys_mat$param_num_list

  # Number of state parameters
  m <- length(sys_mat$state_label)

  # NA in y
  y_isna <- is.na(y)

  if (collapse) {

    # Parameters in the state that will not be used for collapsing
    m2 <- m - length(self_spec_list$state_only)

    # Check if dimensionality of the observation vector is larger than
    # the dimensionality of the state vector
    if (p <= m2) {
      stop(
        paste(
          "`collapse = TRUE` can only be set if the dimensionality of the",
          "observation vector is larger than the dimensionality of the",
          "state vector. Please set `collapse = FALSE`."
        ),
        call. = FALSE
      )
    }

    sys_mat$a_kal <- rbind(matrix(0, m2, 1), sys_mat$a_kal)
    sys_mat$P_inf_kal <- BlockMatrix(matrix(0, m2, m2), sys_mat$P_inf_kal)
    Z_kal_tf <- cbind(
      diag(1, m2, m2), diag(1, m2, m2),
      matrix(0, m2, length(self_spec_list$state_only))
    )
    sys_mat$T_kal <- CombineTRQ(matrix(0, m2, m2), sys_mat$T_kal)
    sys_mat$R_kal <- CombineTRQ(diag(1, m2, m2), sys_mat$R_kal)
    p <- m2

    # Dealing with NA for collapsing transformation
    y_temp <- y
    y_temp[y_isna] <- 0
  }
  m <- m + p # Residuals in state

  # Initialising state vector
  a <- sys_mat$a_kal

  # Initialise P_inf
  P_inf <- sys_mat$P_inf_kal

  # Initialising Q Matrix that depends on the parameters
  Q_kal <- NULL

  # System matrices of components
  T_list <- sys_mat$Tmat
  R_list <- sys_mat$R
  Q_list <- sys_mat[["Q"]]
  Q_list2 <- sys_mat$Q2
  temp_list <- sys_mat$temp_list
  H <- sys_mat[["H"]][["H"]]
  Z_kal <- sys_mat$Z_kal
  T_kal <- sys_mat$T_kal
  R_kal <- sys_mat$R_kal
  P_star <- sys_mat$P_star_kal

  # Initialise for collapsing
  y_kal <- y
  loglik_add <- 0

  ### Function that returns the LogLikelihood ###
  LogLikelihood <- function(param) {

    ## Constructing Q Matrix ##

    # Local Level
    if (local_level_ind && !slope_ind && is.null(level_addvar_list)) {
      if (param_num_list$level > 0) {
        update <- LocalLevel(
          p = p2,
          exclude_level = exclude_level,
          fixed_part = FALSE,
          update_part = TRUE,
          param = param[param_indices$level],
          format_level = format_level,
          decompositions = FALSE
        )
        Q_kal <- BlockMatrix(Q_kal, update[["Q"]])
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$level)
      }
    }

    # Local Level + Slope
    if (slope_ind && is.null(level_addvar_list)) {
      if ((param_num_list$level + param_num_list$slope) > 0) {
        update <- Slope(
          p = p2,
          exclude_level = exclude_level,
          exclude_slope = exclude_slope,
          fixed_part = FALSE,
          update_part = TRUE,
          param = param[param_indices$level],
          format_level = format_level,
          format_slope = format_slope,
          decompositions = FALSE
        )
      }
      if (param_num_list$level > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_level)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$level)
      }
      if (param_num_list$slope > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_slope)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$slope)
      }
    }

    # BSM
    if (length(BSM_vec) > 0) {
      for (i in seq_along(BSM_vec)) {
        s <- BSM_vec[[i]]
        if (param_num_list[[paste0("BSM", s)]] > 0) {
          update <- BSM(
            p = p2,
            s = s,
            exclude_BSM = exclude_BSM_list[[i]],
            fixed_part = FALSE,
            update_part = TRUE,
            param = param[param_indices[[paste0("BSM", s)]]],
            format_BSM = format_BSM_list[[i]],
            decompositions = FALSE
          )
          Q_kal <- BlockMatrix(Q_kal, update[["Q"]])
        } else {
          Q_kal <- BlockMatrix(Q_kal, Q_list2[[paste0("BSM", s)]])
        }
      }
    }

    # Explanatory Variables
    if (!is.null(addvar_list)) {
      if (param_num_list$addvar > 0) {
        update <- AddVar(
          p = p2,
          addvar_list = addvar_list,
          fixed_part = FALSE,
          update_part = TRUE,
          param = param[param_indices$addvar],
          format_addvar = format_addvar,
          decompositions = FALSE
        )
        Q_kal <- BlockMatrix(Q_kal, update[["Q"]])
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$addvar)
      }
    }

    # Local Level + Explanatory Variables
    if (!is.null(level_addvar_list) && !slope_ind) {
      if ((param_num_list$level + param_num_list$level_addvar) > 0) {
        update <- LevelAddVar(
          p = p2,
          exclude_level = exclude_level,
          level_addvar_list = level_addvar_list,
          fixed_part = FALSE,
          update_part = TRUE,
          param = param[param_indices$level],
          format_level = format_level,
          format_level_addvar = format_level_addvar,
          decompositions = FALSE
        )
      }
      if (param_num_list$level > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_level)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$level)
      }
      if (param_num_list$level_addvar > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_level_addvar)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$level_addvar)
      }
    }

    # Local Level + Explanatory Variables + Slope
    if (!is.null(level_addvar_list) && slope_ind) {
      if ((param_num_list$level +
        param_num_list$slope +
        param_num_list$level_addvar) > 0) {
        update <- SlopeAddVar(
          p = p2,
          exclude_level = exclude_level,
          exclude_slope = exclude_slope,
          level_addvar_list = level_addvar_list,
          fixed_part = FALSE,
          update_part = TRUE,
          param = param[param_indices$level],
          format_level = format_level,
          format_slope = format_slope,
          format_level_addvar = format_level_addvar,
          decompositions = FALSE
        )
      }
      if (param_num_list$level > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_level)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$level)
      }
      if (param_num_list$slope > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_slope)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$slope)
      }
      if (param_num_list$level_addvar > 0) {
        Q_kal <- BlockMatrix(Q_kal, update$Q_level_addvar)
      } else {
        Q_kal <- BlockMatrix(Q_kal, Q_list$level_addvar)
      }
    }

    # Cycle
    if (cycle_ind) {
      for (i in seq_along(format_cycle_list)) {
        update <- tryCatch(
          {
            Cycle(
              p = p2,
              exclude_cycle = exclude_cycle_list[[i]],
              damping_factor_ind = damping_factor_ind[[i]],
              fixed_part = FALSE,
              update_part = TRUE,
              param = param[param_indices[[paste0("Cycle", i)]]],
              format_cycle = format_cycle_list[[i]],
              decompositions = FALSE
            )
          },
          error = function(e) {
            NA
          }
        )
        if (all(is.na(update))) {
          return(sqrt(.Machine$double.xmax))
        }
        if (param_num_list[[paste0("Cycle", i)]] > (1 + damping_factor_ind[[i]])) {
          Q_kal <- BlockMatrix(Q_kal, update[["Q"]])
          if (damping_factor_ind[[i]]) {
            if (update$rho >= (1 - 1e-7) || update$rho <= 1e-7) {
              return(sqrt(.Machine$double.xmax))
            }
            P_star <- BlockMatrix(P_star, update$P_star)
          }
        } else {
          Q_kal <- BlockMatrix(Q_kal, Q_list2[[paste0("Cycle", i)]])
        }
        T_kal <- CombineTRQ(T_kal, update$Tmat)
      }
    }

    # ARIMA
    if (!is.null(arima_list)) {
      for (i in seq_along(arima_list)) {
        update <- tryCatch(
          {
            ARIMA(
              p = p2,
              arima_spec = arima_list[[i]],
              exclude_arima = exclude_arima_list[[i]],
              fixed_part = FALSE,
              update_part = TRUE,
              param = param[param_indices[[paste0("ARIMA", i)]]],
              decompositions = FALSE,
              T1 = temp_list[[paste0("ARIMA", i)]]$T1,
              T2 = temp_list[[paste0("ARIMA", i)]]$T2,
              T3 = temp_list[[paste0("ARIMA", i)]]$T3,
              R1 = temp_list[[paste0("ARIMA", i)]]$R1,
              R2 = temp_list[[paste0("ARIMA", i)]]$R2
            )
          },
          error = function(e) {
            NA
          }
        )
        if (all(is.na(update))) {
          return(sqrt(.Machine$double.xmax))
        }
        Q_kal <- BlockMatrix(Q_kal, update[["Q"]])
        P_star <- BlockMatrix(P_star, update$P_star)
        if (arima_list[[i]][[1]] == 0 && arima_list[[i]][[3]] == 0) {
          T_kal <- CombineTRQ(T_kal, T_list[[paste0("ARIMA", i)]])
          R_kal <- BlockMatrix(R_kal, R_list[[paste0("ARIMA", i)]])
        } else {
          T_kal <- CombineTRQ(T_kal, update$Tmat)
          R_kal <- BlockMatrix(R_kal, update$R)
        }
      }
    }

    # SARIMA
    if (!is.null(sarima_list)) {
      for (i in seq_along(sarima_list)) {
        update <- tryCatch(
          {
            SARIMA(
              p = p2,
              sarima_spec = sarima_list[[i]],
              exclude_sarima = exclude_sarima_list[[i]],
              fixed_part = FALSE,
              update_part = TRUE,
              param = param[param_indices[[paste0("SARIMA", i)]]],
              decompositions = FALSE,
              T1 = temp_list[[paste0("SARIMA", i)]]$T1,
              T2 = temp_list[[paste0("SARIMA", i)]]$T2,
              T3 = temp_list[[paste0("SARIMA", i)]]$T3,
              R1 = temp_list[[paste0("SARIMA", i)]]$R1,
              R2 = temp_list[[paste0("SARIMA", i)]]$R2
            )
          },
          error = function(e) {
            NA
          }
        )
        if (all(is.na(update))) {
          return(sqrt(.Machine$double.xmax))
        }
        Q_kal <- BlockMatrix(Q_kal, update[["Q"]])
        P_star <- BlockMatrix(P_star, update$P_star)
        if (sum(sarima_list[[i]]$ar) == 0 && sum(sarima_list[[i]]$ma) == 0) {
          T_kal <- CombineTRQ(T_kal, T_list[[paste0("SARIMA", i)]])
          R_kal <- BlockMatrix(R_kal, R_list[[paste0("SARIMA", i)]])
        } else {
          T_kal <- CombineTRQ(T_kal, update$Tmat)
          R_kal <- BlockMatrix(R_kal, update$R)
        }
      }
    }

    # Self Specified
    if (!is.null(self_spec_list)) {

      # System matrices that depend on parameters
      if (!is.null(self_spec_list$sys_mat_fun)) {
        input <- list(param = param[param_indices$self_spec])
        if (!is.null(self_spec_list$sys_mat_input)) {
          input <- c(input, self_spec_list$sys_mat_input)
        }
        update <- tryCatch(
          {
            do.call(self_spec_list$sys_mat_fun, input)
          },
          error = function(e) {
            NA
          }
        )
        if (all(is.na(update))) {
          return(sqrt(.Machine$double.xmax))
        }

        # Adding to full system matrices
        if (!is.null(update$Z)) {
          Z_kal <- CombineZ(Z_kal, update$Z)
        }
        if (!is.null(update$Tmat)) {
          T_kal <- CombineTRQ(T_kal, update$Tmat)
        }
        if (!is.null(update$R)) {
          R_kal <- CombineTRQ(R_kal, update$R)
        }
        if (!is.null(update[["Q"]])) {
          Q_kal <- CombineTRQ(Q_kal, update[["Q"]])
        }
        if (!is.null(update$a1)) {
          a <- rbind(a, update$a1)
        }
        if (!is.null(update$P_star)) {
          P_star <- BlockMatrix(P_star, update$P_star)
        }
      }

      # System matrices that do not depend on parameters
      if (!is.null(T_list$self_spec)) {
        T_kal <- CombineTRQ(T_kal, T_list$self_spec)
      }
      if (!is.null(R_list$self_spec)) {
        R_kal <- CombineTRQ(R_kal, R_list$self_spec)
      }
      if (!is.null(Q_list$self_spec)) {
        Q_kal <- CombineTRQ(Q_kal, Q_list$self_spec)
      }
      if (!is.null(self_spec_list$P_star)) {
        P_star <- BlockMatrix(P_star, self_spec_list$P_star)
      }

      # Check for state only parameters
      if (!is.null(self_spec_list$state_only) && collapse) {
        state_only_indices <- dim(a)[[1]] - p - self_spec_list$state_num +
          self_spec_list$state_only
        if (is.matrix(Z_kal)) {
          Z_kal <- Z_kal[, -state_only_indices, drop = FALSE]
        } else {
          Z_kal <- Z_kal[, -state_only_indices, , drop = FALSE]
        }
      }
    }

    # H Matrix
    if (!sys_mat$H_spec) {
      if (param_num_list$H > 0) {
        H <- Cholesky(
          param = param[param_indices$H],
          format = H_format,
          decompositions = FALSE
        )
        if (all(is.na(H))) {
          return(sqrt(.Machine$double.xmax))
        }
        if (!collapse) {
          P_star <- BlockMatrix(H, P_star)
        }
      }
    } else if (is.null(self_spec_list[["H"]])) {
      H <- update[["H"]]
      if (!collapse) {
        if (is.matrix(H)) {
          P_star <- BlockMatrix(H, P_star)
        } else {
          P_star <- BlockMatrix(H[, , 1], P_star)
        }
      }
    }
    if (!collapse) {
      if (is.matrix(H)) {
        Q_kal <- CombineTRQ(H, Q_kal)
      } else {
        Q_kal <- CombineTRQ(H[, , c(2:N, 1), drop = FALSE], Q_kal)
      }
    }

    # Check for finite values in Q_kal and H
    if (!all(is.finite(Q_kal)) || !all(is.finite(H))) {
      return(sqrt(.Machine$double.xmax))
    }

    # Collapse observation vector
    if (collapse) {
      if (is.matrix(H) && is.matrix(Z_kal)) {
        Hinv <- solve(H)
        ZtHinv <- crossprod(Z_kal, Hinv)
        A_star <- solve(ZtHinv %*% Z_kal) %*% ZtHinv
        y_kal <- tcrossprod(y_temp, A_star)
        H_star <- tcrossprod(A_star %*% H, A_star)
        P_star <- BlockMatrix(H_star, P_star)
        Q_kal <- CombineTRQ(H_star, Q_kal)

        # loglikelihood contribution
        for (i in 1:N) {
          e <- y_temp[i, ] - Z_kal %*% y_kal[i, ]
          loglik_add <- loglik_add - 0.5 * crossprod(e, Hinv %*% e)
        }
        y_kal[y_kal == 0] <- NA
        loglik_add <- loglik_add + N * 0.5 * log(det(H_star) / det(H)) -
          0.5 * (sum(!is.na(y)) - sum(!is.na(y_kal))) * log(2 * pi)
      } else if (is.matrix(H) && !is.matrix(Z_kal)) {
        y_kal <- matrix(0, N, m2)
        H_star <- array(0, dim = c(m2, m2, N))
        Hinv <- solve(H)
        detH <- det(H)
        for (i in 1:N) {
          Z_t <- matrix(Z_kal[, , i], nrow = p2)
          ZtHinv <- crossprod(Z_t, Hinv)
          A_star <- solve(ZtHinv %*% Z_t) %*% ZtHinv
          y_kal[i, ] <- A_star %*% y_temp[i, ]
          H_star[, , i] <- tcrossprod(A_star %*% H, A_star)
          e <- y_temp[i, ] - Z_t %*% y_kal[i, ]
          loglik_add <- loglik_add +
            0.5 * log(det(H_star[, , i]) / detH) -
            0.5 * crossprod(e, Hinv %*% e)
        }
        y_kal[y_kal == 0] <- NA
        loglik_add <- loglik_add -
          0.5 * (sum(!is.na(y)) - sum(!is.na(y_kal))) * log(2 * pi)
        P_star <- BlockMatrix(H_star[, , 1], P_star)
        Q_kal <- CombineTRQ(H_star[, , c(2:N, 1), drop = FALSE], Q_kal)
      } else if (!is.matrix(H) && is.matrix(Z_kal)) {
        y_kal <- matrix(0, N, m2)
        H_star <- array(0, dim = c(m2, m2, N))
        for (i in 1:N) {
          H_t <- as.matrix(H[, , i])
          Hinv <- solve(H_t)
          detH <- det(H_t)
          ZtHinv <- crossprod(Z_kal, Hinv)
          A_star <- solve(ZtHinv %*% Z_kal) %*% ZtHinv
          y_kal[i, ] <- A_star %*% y_temp[i, ]
          H_star[, , i] <- tcrossprod(A_star %*% H_t, A_star)
          e <- y_temp[i, ] - Z_kal %*% y_kal[i, ]
          loglik_add <- loglik_add +
            0.5 * log(det(H_star[, , i]) / detH) -
            0.5 * crossprod(e, Hinv %*% e)
        }
        y_kal[y_kal == 0] <- NA
        loglik_add <- loglik_add -
          0.5 * (sum(!is.na(y)) - sum(!is.na(y_kal))) * log(2 * pi)
        P_star <- BlockMatrix(H_star[, , 1], P_star)
        Q_kal <- CombineTRQ(H_star[, , c(2:N, 1), drop = FALSE], Q_kal)
      } else {
        y_kal <- matrix(0, N, m2)
        H_star <- array(0, dim = c(m2, m2, N))
        for (i in 1:N) {
          H_t <- as.matrix(H[, , i])
          Hinv <- solve(H_t)
          detH <- det(H_t)
          Z_t <- matrix(Z_kal[, , i], nrow = p2)
          ZtHinv <- crossprod(Z_t, Hinv)
          A_star <- solve(ZtHinv %*% Z_t) %*% ZtHinv
          y_kal[i, ] <- A_star %*% y_temp[i, ]
          H_star[, , i] <- tcrossprod(A_star %*% H_t, A_star)
          e <- y_temp[i, ] - Z_t %*% y_kal[i, ]
          loglik_add <- loglik_add +
            0.5 * log(det(H_star[, , i]) / detH) -
            0.5 * crossprod(e, Hinv %*% e)
        }
        y_kal[y_kal == 0] <- NA
        loglik_add <- loglik_add -
          0.5 * (sum(!is.na(y)) - sum(!is.na(y_kal))) * log(2 * pi)
        P_star <- BlockMatrix(H_star[, , 1], P_star)
        Q_kal <- CombineTRQ(H_star[, , c(2:N, 1), drop = FALSE], Q_kal)
      }
      Z_kal <- Z_kal_tf
      y_isna <- is.na(y_kal)
    }

    # Augment system matrices to arrays
    if (is.matrix(Z_kal)) {
      dim(Z_kal) <- c(dim(Z_kal), 1)
    }
    if (is.matrix(T_kal)) {
      dim(T_kal) <- c(dim(T_kal), 1)
    }
    if (is.matrix(R_kal)) {
      dim(R_kal) <- c(dim(R_kal), 1)
    }
    if (is.matrix(Q_kal)) {
      dim(Q_kal) <- c(dim(Q_kal), 1)
    }

    # Kalman Filter to obtain LogLikelihood
    loglik <- LogLikC(
      y = y_kal,
      y_isna = y_isna,
      a = a,
      P_inf = P_inf,
      P_star = P_star,
      Z = Z_kal,
      T = T_kal,
      R = R_kal,
      Q = Q_kal
    )

    # Check for NA returned by LogLikC
    if (is.na(loglik)) {
      return(sqrt(.Machine$double.xmax))
    }

    # Return the negative average loglikelihood
    # Note: The average is computed as sum / N, using mean would divide by N*p
    return(-loglik - loglik_add / N)
  }

  # Checking the number of initial parameters specified
  if (length(initial) < sys_mat$param_num) {
    if (verbose) {
      warning(
        paste0(
          "Number of initial parameters is less than the required ",
          "amount of parameters (", sys_mat$param_num, "), ",
          "recycling the initial parameters the required amount of times."
        ),
        call. = FALSE
      )
    }
    initial <- rep(
      initial,
      ceiling(sys_mat$param_num / length(initial))
    )[1:sys_mat$param_num]
  } else if (length(initial) > sys_mat$param_num) {
    if (verbose) {
      warning(
        paste0(
          "Number of initial parameters is greater than the required ",
          "amount of parameters (", sys_mat$param_num, "), ",
          "only using the first ", sys_mat$param_num, " initial parameters."
        ),
        call. = FALSE
      )
    }
    initial <- initial[1:sys_mat$param_num]
  }

  if (fit) {
    if (verbose) {
      # Keeping track of the elapsed time of the optim function
      t1 <- Sys.time()
      message(paste0("Starting the optimisation procedure at: ", t1))
    }

    # Optimising parameters
    fit_model <- optim_fun(
      par = initial,
      fn = LogLikelihood,
      method = method,
      control = control
    )

    # Check if optim returned NA values as optimal parameters
    if (any(is.na(fit_model$par))) {
      stop(
        "NA values returned by `optim`. Please try different initial values.",
        call. = FALSE
      )
    }

    if (verbose) {
      # Elapsed time
      t2 <- Sys.time()
      message(paste("Finished the optimisation procedure at:", t2))
      message(paste0("Time difference of ", t2 - t1, " ", units(t2 - t1), "\n"))
    }

    model_par <- fit_model$par
  } else {
    model_par <- initial
  }

  # Obtain components of the state space model
  result <- do.call(
    StateSpaceEval,
    c(
      list(
        param = model_par, y = y, diagnostics = diagnostics
      ),
      sys_mat$function_call
    )
  )
  if (fit) result$optim <- fit_model

  # (Adjusted) Input parameters that were passed on to statespacer
  result$function_call <- c(
    list(y = y),
    sys_mat$function_call,
    list(
      fit = fit,
      initial = initial,
      method = method,
      control = control,
      collapse = collapse,
      diagnostics = diagnostics,
      standard_errors = standard_errors,
      verbose = verbose
    )
  )

  # Loglikelihood function
  result$loglik_fun <- function(param) -N * LogLikelihood(param)

  # Indices of the parameters for each of the components
  result$diagnostics$param_indices <- param_indices

  # Compute standard errors if required
  if (standard_errors) {

    # Initialise standard_errors list
    standard_errors <- list()
    standard_errors$Q <- list()
    standard_errors$Q_loading_matrix <- list()
    standard_errors$Q_diagonal_matrix <- list()
    standard_errors$Q_correlation_matrix <- list()
    standard_errors$Q_stdev_matrix <- list()
    standard_errors$lambda <- list()
    standard_errors$rho <- list()
    standard_errors$AR <- list()
    standard_errors$MA <- list()
    standard_errors$SAR <- list()
    standard_errors$SMA <- list()
    standard_errors$H <- list()

    # Hessian of the loglikelihood evaluated at the ML estimates
    hessian <- numDeriv::hessian(
      func = result$loglik_fun,
      x = model_par
    )
    result$diagnostics$hessian <- hessian
    min_hessian_inv <- -solve(hessian)

    # Local Level
    if (local_level_ind && !slope_ind && is.null(level_addvar_list)) {
      if (param_num_list$level > 0) {
        TransformFun <- function(param) {
          update <- LocalLevel(
            p = p2,
            exclude_level = exclude_level,
            fixed_part = FALSE,
            update_part = TRUE,
            param = param,
            format_level = format_level,
            decompositions = TRUE
          )
          result <- c(
            update[["Q"]],
            update$loading_matrix, update$diagonal_matrix,
            update$correlation_matrix, update$stdev_matrix
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices$level]
        )
        hess_subset <- min_hessian_inv[param_indices$level,
          param_indices$level,
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))
        dimQ <- dim(result$system_matrices$Q$level)[[1]]
        se_index <- 1:(dimQ * dimQ)

        # Q
        standard_errors$Q$level <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_loading_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_loading_matrix$level <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_diagonal_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_diagonal_matrix$level <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_correlation_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_correlation_matrix$level <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_stdev_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_stdev_matrix$level <- matrix(
          std_errors[se_index], dimQ, dimQ
        )
      }
    }

    # Local Level + Slope
    if (slope_ind && is.null(level_addvar_list)) {
      if ((param_num_list$level + param_num_list$slope) > 0) {
        TransformFun <- function(param) {
          update <- Slope(
            p = p2,
            exclude_level = exclude_level,
            exclude_slope = exclude_slope,
            fixed_part = FALSE,
            update_part = TRUE,
            param = param,
            format_level = format_level,
            format_slope = format_slope,
            decompositions = TRUE
          )
          result <- c(
            update$Q_level,
            update$loading_matrix_level, update$diagonal_matrix_level,
            update$correlation_matrix_level, update$stdev_matrix_level,
            update$Q_slope,
            update$loading_matrix_slope, update$diagonal_matrix_slope,
            update$correlation_matrix_slope, update$stdev_matrix_slope
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices$level]
        )
        hess_subset <- min_hessian_inv[param_indices$level,
          param_indices$level,
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))

        # level
        if (param_num_list$level > 0) {
          dimQ <- dim(result$system_matrices$Q$level)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_level
          standard_errors$Q$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          std_errors <- std_errors[-se_index]
        }

        # slope
        if (param_num_list$slope > 0) {
          dimQ <- dim(result$system_matrices$Q$slope)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_slope
          standard_errors$Q$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )
        }
      }
    }

    # BSM
    if (length(BSM_vec) > 0) {
      for (i in seq_along(BSM_vec)) {
        s <- BSM_vec[[i]]
        if (param_num_list[[paste0("BSM", s)]] > 0) {
          TransformFun <- function(param) {
            update <- BSM(
              p = p2,
              s = s,
              exclude_BSM = exclude_BSM_list[[i]],
              fixed_part = FALSE,
              update_part = TRUE,
              transform_only = TRUE,
              param = param,
              format_BSM = format_BSM_list[[i]],
              decompositions = TRUE
            )
            result <- c(
              update$Q_BSM,
              update$loading_matrix, update$diagonal_matrix,
              update$correlation_matrix, update$stdev_matrix
            )
            return(result)
          }
          jacobian <- numDeriv::jacobian(
            func = TransformFun,
            x = model_par[param_indices[[paste0("BSM", s)]]]
          )
          hess_subset <- min_hessian_inv[param_indices[[paste0("BSM", s)]],
            param_indices[[paste0("BSM", s)]],
            drop = FALSE
          ]
          std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))
          dimQ <- dim(result$system_matrices$Q[[paste0("BSM", s)]])[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q
          standard_errors$Q[[paste0("BSM", s)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix[[paste0("BSM", s)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix[[paste0("BSM", s)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix[[paste0("BSM", s)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix[[paste0("BSM", s)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )
        }
      }
    }

    # Explanatory Variables
    if (!is.null(addvar_list)) {
      if (param_num_list$addvar > 0) {
        TransformFun <- function(param) {
          update <- AddVar(
            p = p2,
            addvar_list = addvar_list,
            fixed_part = FALSE,
            update_part = TRUE,
            param = param,
            format_addvar = format_addvar,
            decompositions = TRUE
          )
          result <- c(
            update[["Q"]],
            update$loading_matrix, update$diagonal_matrix,
            update$correlation_matrix, update$stdev_matrix
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices$addvar]
        )
        hess_subset <- min_hessian_inv[param_indices$addvar,
          param_indices$addvar,
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))
        dimQ <- dim(result$system_matrices$Q$addvar)[[1]]
        se_index <- 1:(dimQ * dimQ)

        # Q
        standard_errors$Q$addvar <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_loading_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_loading_matrix$addvar <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_diagonal_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_diagonal_matrix$addvar <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_correlation_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_correlation_matrix$addvar <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_stdev_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_stdev_matrix$addvar <- matrix(
          std_errors[se_index], dimQ, dimQ
        )
      }
    }

    # Local Level + Explanatory Variables
    if (!is.null(level_addvar_list) && !slope_ind) {
      if ((param_num_list$level + param_num_list$level_addvar) > 0) {
        TransformFun <- function(param) {
          update <- LevelAddVar(
            p = p2,
            exclude_level = exclude_level,
            level_addvar_list = level_addvar_list,
            fixed_part = FALSE,
            update_part = TRUE,
            param = param,
            format_level = format_level,
            format_level_addvar = format_level_addvar,
            decompositions = TRUE
          )
          result <- c(
            update$Q_level,
            update$loading_matrix_level,
            update$diagonal_matrix_level,
            update$correlation_matrix_level,
            update$stdev_matrix_level,
            update$Q_level_addvar,
            update$loading_matrix_level_addvar,
            update$diagonal_matrix_level_addvar,
            update$correlation_matrix_level_addvar,
            update$stdev_matrix_level_addvar
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices$level]
        )
        hess_subset <- min_hessian_inv[param_indices$level,
          param_indices$level,
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))

        # level
        if (param_num_list$level > 0) {
          dimQ <- dim(result$system_matrices$Q$level)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_level
          standard_errors$Q$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          std_errors <- std_errors[-se_index]
        }

        # level_addvar
        if (param_num_list$level_addvar > 0) {
          dimQ <- dim(result$system_matrices$Q$level_addvar)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_level_addvar
          standard_errors$Q$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )
        }
      }
    }

    # Local Level + Explanatory Variables + Slope
    if (!is.null(level_addvar_list) && slope_ind) {
      if ((param_num_list$level +
        param_num_list$slope +
        param_num_list$level_addvar) > 0) {
        TransformFun <- function(param) {
          update <- SlopeAddVar(
            p = p2,
            exclude_level = exclude_level,
            exclude_slope = exclude_slope,
            level_addvar_list = level_addvar_list,
            fixed_part = FALSE,
            update_part = TRUE,
            param = param,
            format_level = format_level,
            format_slope = format_slope,
            format_level_addvar = format_level_addvar,
            decompositions = TRUE
          )
          result <- c(
            update$Q_level,
            update$loading_matrix_level,
            update$diagonal_matrix_level,
            update$correlation_matrix_level,
            update$stdev_matrix_level,
            update$Q_slope,
            update$loading_matrix_slope,
            update$diagonal_matrix_slope,
            update$correlation_matrix_slope,
            update$stdev_matrix_slope,
            update$Q_level_addvar,
            update$loading_matrix_level_addvar,
            update$diagonal_matrix_level_addvar,
            update$correlation_matrix_level_addvar,
            update$stdev_matrix_level_addvar
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices$level]
        )
        hess_subset <- min_hessian_inv[param_indices$level,
          param_indices$level,
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))

        # level
        if (param_num_list$level > 0) {
          dimQ <- dim(result$system_matrices$Q$level)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_level
          standard_errors$Q$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_level
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$level <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          std_errors <- std_errors[-se_index]
        }

        # slope
        if (param_num_list$slope > 0) {
          dimQ <- dim(result$system_matrices$Q$slope)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_slope
          standard_errors$Q$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_slope
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$slope <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          std_errors <- std_errors[-se_index]
        }

        # level_addvar
        if (param_num_list$level_addvar > 0) {
          dimQ <- dim(result$system_matrices$Q$level_addvar)[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q_level_addvar
          standard_errors$Q$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix_level_addvar
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix$level_addvar <- matrix(
            std_errors[se_index], dimQ, dimQ
          )
        }
      }
    }

    # Cycle
    if (cycle_ind) {
      for (i in seq_along(format_cycle_list)) {
        TransformFun <- function(param) {
          update <- Cycle(
            p = p2,
            exclude_cycle = exclude_cycle_list[[i]],
            damping_factor_ind = damping_factor_ind[[i]],
            fixed_part = FALSE,
            update_part = TRUE,
            transform_only = TRUE,
            param = param,
            format_cycle = format_cycle_list[[i]],
            decompositions = TRUE
          )
          result <- c(
            update$lambda, update$rho, update$Q_cycle,
            update$loading_matrix, update$diagonal_matrix,
            update$correlation_matrix, update$stdev_matrix
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices[[paste0("Cycle", i)]]]
        )
        hess_subset <- min_hessian_inv[param_indices[[paste0("Cycle", i)]],
          param_indices[[paste0("Cycle", i)]],
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))

        # lambda
        standard_errors$lambda[[paste0("Cycle", i)]] <- std_errors[[1]]
        std_errors <- std_errors[-1]

        # rho
        if (damping_factor_ind[[i]]) {
          standard_errors$rho[[paste0("Cycle", i)]] <- std_errors[[1]]
          std_errors <- std_errors[-1]
        }

        if (param_num_list[[paste0("Cycle", i)]] > (1 + damping_factor_ind[[i]])) {
          dimQ <- dim(result$system_matrices$Q[[paste0("Cycle", i)]])[[1]]
          se_index <- 1:(dimQ * dimQ)

          # Q
          standard_errors$Q[[paste0("Cycle", i)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_loading_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_loading_matrix[[paste0("Cycle", i)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_diagonal_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_diagonal_matrix[[paste0("Cycle", i)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_correlation_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_correlation_matrix[[paste0("Cycle", i)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )

          # Q_stdev_matrix
          std_errors <- std_errors[-se_index]
          standard_errors$Q_stdev_matrix[[paste0("Cycle", i)]] <- matrix(
            std_errors[se_index], dimQ, dimQ
          )
        }
      }
    }

    # ARIMA
    if (!is.null(arima_list)) {
      for (i in seq_along(arima_list)) {
        TransformFun <- function(param) {
          update <- ARIMA(
            p = p2,
            arima_spec = arima_list[[i]],
            exclude_arima = exclude_arima_list[[i]],
            fixed_part = FALSE,
            update_part = TRUE,
            transform_only = TRUE,
            param = param,
            decompositions = TRUE
          )
          result <- c(
            update$ar, update$ma, update$Q,
            update$loading_matrix, update$diagonal_matrix,
            update$correlation_matrix, update$stdev_matrix
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices[[paste0("ARIMA", i)]]]
        )
        hess_subset <- min_hessian_inv[param_indices[[paste0("ARIMA", i)]],
          param_indices[[paste0("ARIMA", i)]],
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))

        # AR
        if (!is.null(result$system_matrices$AR[[paste0("ARIMA", i)]])) {
          se_index <- 1:length(result$system_matrices$AR[[paste0("ARIMA", i)]])
          if (is.vector(result$system_matrices$AR[[paste0("ARIMA", i)]])) {
            standard_errors$AR[[paste0("ARIMA", i)]] <- std_errors[se_index]
          } else {
            standard_errors$AR[[paste0("ARIMA", i)]] <- array(
              std_errors[se_index],
              dim = dim(result$system_matrices$AR[[paste0("ARIMA", i)]])
            )
          }
          std_errors <- std_errors[-se_index]
        }

        # MA
        if (!is.null(result$system_matrices$MA[[paste0("ARIMA", i)]])) {
          se_index <- 1:length(result$system_matrices$MA[[paste0("ARIMA", i)]])
          if (is.vector(result$system_matrices$MA[[paste0("ARIMA", i)]])) {
            standard_errors$MA[[paste0("ARIMA", i)]] <- std_errors[se_index]
          } else {
            standard_errors$MA[[paste0("ARIMA", i)]] <- array(
              std_errors[se_index],
              dim = dim(result$system_matrices$MA[[paste0("ARIMA", i)]])
            )
          }
          std_errors <- std_errors[-se_index]
        }

        dimQ <- dim(result$system_matrices$Q[[paste0("ARIMA", i)]])[[1]]
        se_index <- 1:(dimQ * dimQ)

        # Q
        standard_errors$Q[[paste0("ARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_loading_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_loading_matrix[[paste0("ARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_diagonal_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_diagonal_matrix[[paste0("ARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_correlation_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_correlation_matrix[[paste0("ARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_stdev_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_stdev_matrix[[paste0("ARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )
      }
    }

    # SARIMA
    if (!is.null(sarima_list)) {
      for (i in seq_along(sarima_list)) {
        TransformFun <- function(param) {
          update <- SARIMA(
            p = p2,
            sarima_spec = sarima_list[[i]],
            exclude_sarima = exclude_sarima_list[[i]],
            fixed_part = FALSE,
            update_part = TRUE,
            transform_only = TRUE,
            param = param,
            decompositions = TRUE
          )
          result <- c()
          if (length(update$sar) > 0) {
            for (sar in update$sar) {
              result <- c(result, sar)
            }
          }
          if (length(update$sma) > 0) {
            for (sma in update$sma) {
              result <- c(result, sma)
            }
          }
          result <- c(
            result, update$Q,
            update$loading_matrix, update$diagonal_matrix,
            update$correlation_matrix, update$stdev_matrix
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices[[paste0("SARIMA", i)]]]
        )
        hess_subset <- min_hessian_inv[param_indices[[paste0("SARIMA", i)]],
          param_indices[[paste0("SARIMA", i)]],
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))

        # SAR
        if (!is.null(result$system_matrices$SAR[[paste0("SARIMA", i)]])) {
          standard_errors$SAR[[paste0("SARIMA", i)]] <- list()
          for (j in seq_along(result$system_matrices$SAR[[paste0("SARIMA", i)]])) {
            sar <- result$system_matrices$SAR[[paste0("SARIMA", i)]][[j]]
            se_index <- 1:length(sar)
            if (is.vector(sar)) {
              standard_errors$SAR[[paste0("SARIMA", i)]][[j]] <- std_errors[se_index]
            } else {
              standard_errors$SAR[[paste0("SARIMA", i)]][[j]] <- array(
                std_errors[se_index],
                dim = dim(sar)
              )
            }
            std_errors <- std_errors[-se_index]
          }
          names(standard_errors$SAR[[paste0("SARIMA", i)]]) <- names(
            result$system_matrices$SAR[[paste0("SARIMA", i)]]
          )
        }

        # SMA
        if (!is.null(result$system_matrices$SMA[[paste0("SARIMA", i)]])) {
          standard_errors$SMA[[paste0("SARIMA", i)]] <- list()
          for (j in seq_along(result$system_matrices$SMA[[paste0("SARIMA", i)]])) {
            sma <- result$system_matrices$SMA[[paste0("SARIMA", i)]][[j]]
            se_index <- 1:length(sma)
            if (is.vector(sma)) {
              standard_errors$SMA[[paste0("SARIMA", i)]][[j]] <- std_errors[se_index]
            } else {
              standard_errors$SMA[[paste0("SARIMA", i)]][[j]] <- array(
                std_errors[se_index],
                dim = dim(sma)
              )
            }
            std_errors <- std_errors[-se_index]
          }
          names(standard_errors$SMA[[paste0("SARIMA", i)]]) <- names(
            result$system_matrices$SMA[[paste0("SARIMA", i)]]
          )
        }

        dimQ <- dim(result$system_matrices$Q[[paste0("SARIMA", i)]])[[1]]
        se_index <- 1:(dimQ * dimQ)

        # Q
        standard_errors$Q[[paste0("SARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_loading_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_loading_matrix[[paste0("SARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_diagonal_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_diagonal_matrix[[paste0("SARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_correlation_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_correlation_matrix[[paste0("SARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )

        # Q_stdev_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$Q_stdev_matrix[[paste0("SARIMA", i)]] <- matrix(
          std_errors[se_index], dimQ, dimQ
        )
      }
    }

    # Self Specified
    if (!is.null(self_spec_list$transform_fun)) {
      input <- list(
        func = self_spec_list$transform_fun,
        x = model_par[param_indices$self_spec]
      )
      if (!is.null(self_spec_list$transform_input)) {
        input <- c(input, self_spec_list$transform_input)
      }
      jacobian <- do.call(numDeriv::jacobian, input)
      hess_subset <- min_hessian_inv[param_indices$self_spec,
        param_indices$self_spec,
        drop = FALSE
      ]
      standard_errors$self_spec <- sqrt(
        diag(tcrossprod(jacobian %*% hess_subset, jacobian))
      )
    }

    # H Matrix
    if (!sys_mat$H_spec) {
      if (param_num_list$H > 0) {
        TransformFun <- function(param) {
          update <- Cholesky(
            param = param,
            format = H_format,
            decompositions = TRUE
          )
          result <- c(
            update$cov_mat,
            update$loading_matrix, update$diagonal_matrix,
            update$correlation_matrix, update$stdev_matrix
          )
          return(result)
        }
        jacobian <- numDeriv::jacobian(
          func = TransformFun,
          x = model_par[param_indices$H]
        )
        hess_subset <- min_hessian_inv[param_indices$H,
          param_indices$H,
          drop = FALSE
        ]
        std_errors <- sqrt(diag(tcrossprod(jacobian %*% hess_subset, jacobian)))
        dimH <- dim(result$system_matrices$H$H)[[1]]
        se_index <- 1:(dimH * dimH)

        # H
        standard_errors$H$H <- matrix(
          std_errors[se_index], dimH, dimH
        )

        # loading_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$H$loading_matrix <- matrix(
          std_errors[se_index], dimH, dimH
        )

        # diagonal_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$H$diagonal_matrix <- matrix(
          std_errors[se_index], dimH, dimH
        )

        # correlation_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$H$correlation_matrix <- matrix(
          std_errors[se_index], dimH, dimH
        )

        # stdev_matrix
        std_errors <- std_errors[-se_index]
        standard_errors$H$stdev_matrix <- matrix(
          std_errors[se_index], dimH, dimH
        )
      }
    }

    # Remove 0 length elements from standard_errors list
    if (length(standard_errors$Q) == 0) {
      standard_errors$Q <- NULL
    }
    if (length(standard_errors$Q_loading_matrix) == 0) {
      standard_errors$Q_loading_matrix <- NULL
    }
    if (length(standard_errors$Q_diagonal_matrix) == 0) {
      standard_errors$Q_diagonal_matrix <- NULL
    }
    if (length(standard_errors$Q_correlation_matrix) == 0) {
      standard_errors$Q_correlation_matrix <- NULL
    }
    if (length(standard_errors$Q_stdev_matrix) == 0) {
      standard_errors$Q_stdev_matrix <- NULL
    }
    if (length(standard_errors$lambda) == 0) {
      standard_errors$lambda <- NULL
    }
    if (length(standard_errors$rho) == 0) {
      standard_errors$rho <- NULL
    }
    if (length(standard_errors$AR) == 0) {
      standard_errors$AR <- NULL
    }
    if (length(standard_errors$MA) == 0) {
      standard_errors$MA <- NULL
    }
    if (length(standard_errors$SAR) == 0) {
      standard_errors$SAR <- NULL
    }
    if (length(standard_errors$SMA) == 0) {
      standard_errors$SMA <- NULL
    }
    if (length(standard_errors$H) == 0) {
      standard_errors$H <- NULL
    }

    # Add standard_errors to result
    result$standard_errors <- standard_errors
  }

  class(result) <- "statespacer"
  return(result)
}

Try the statespacer package in your browser

Any scripts or data that you put into this service are public.

statespacer documentation built on Feb. 16, 2023, 9:48 p.m.