R/generateParams.R

Defines functions smart_ind random_ind smart_weightpars random_weightpars smart_distpars random_distpars smart_impactmat random_impactmat smart_covmat random_covmat random_coefmats2 random_coefmats

Documented in random_coefmats random_coefmats2 random_covmat random_distpars random_impactmat random_ind random_weightpars smart_covmat smart_distpars smart_impactmat smart_ind smart_weightpars

#' @title Create random VAR model \eqn{(dxd)} coefficient matrices \eqn{A}.
#'
#' @description \code{random_coefmats} generates random VAR model coefficient matrices.
#'
#' @inheritParams loglikelihood
#' @param how_many how many \eqn{(dxd)} coefficient matrices \eqn{A} should be drawn?
#' @param scale non-diagonal elements will be drawn from mean zero normal distribution
#'   with \code{sd=0.3/scale} and diagonal elements from one with \code{sd=0.6/scale}.
#'   Larger scale will hence more likely result stationary coefficient matrices, but
#'   will explore smaller area of the parameter space. Can be for example
#'   \code{1 + log(2*mean(c((p-0.2)^(1.25), d)))}.
#' @return Returns \eqn{((how_many*d^2)x1)} vector containing vectorized coefficient
#'  matrices \eqn{(vec(A_{1}),...,vec(A_{how_many}))}. Note that if \code{how_many==p},
#'  then the returned vector equals \strong{\eqn{\phi_{m}}}.
#' @keywords internal

random_coefmats <- function(d, how_many, scale) {
  as.vector(vapply(1:how_many, function(i1) {
    x <- rnorm(d*d, mean=0, sd=0.3/scale)
    x[1 + 0:(d - 1) * (d + 1)] <- rnorm(d, mean=0, sd=0.6/scale)
    x
  }, numeric(d*d)))
}


#' @title Create random stationary VAR model \eqn{(dxd)} coefficient matrices \eqn{A}.
#'
#' @description \code{random_coefmats2} generates random VAR model coefficient matrices.
#'
#' @inheritParams loglikelihood
#' @param ar_scale a positive real number. Larger values will typically result larger AR coefficients.
#' @details The coefficient matrices are generated using the algorithm proposed by Ansley
#'   and Kohn (1986) which forces stationarity. It's not clear in detail how \code{ar_scale}
#'   affects the coefficient matrices. Read the cited article by Ansley and Kohn (1986) and
#'   the source code for more information.
#'
#'   Note that when using large \code{ar_scale} with large \code{p} or \code{d}, numerical
#'   inaccuracies caused by the imprecision of the float-point presentation may result in errors
#'   or nonstationary AR-matrices. Using smaller \code{ar_scale} facilitates the usage of larger
#'   \code{p} or \code{d}.
#' @return Returns \eqn{((pd^2)x1)} vector containing stationary vectorized coefficient
#'  matrices \eqn{(vec(A_{1}),...,vec(A_{p})}.
#' @references
#'  \itemize{
#'    \item Ansley C.F., Kohn R. 1986. A note on reparameterizing a vector autoregressive
#'       moving average model to enforce stationarity.
#'       \emph{Journal of statistical computation and simulation}, \strong{24}:2, 99-106.
#'  }
#' @keywords internal

random_coefmats2 <- function(p, d, ar_scale=1) {
  # First generate matrices P_1,..,P_p with singular values less than one
  stopifnot(ar_scale > 0 && ar_scale <= 1)
  Id <- diag(nrow=d)
  all_P <- array(dim=c(d, d, p))
  for(i1 in 1:p) {
    A <- matrix(rnorm(d*d, sd=ar_scale), nrow=d)
    B <- t(chol(Id + tcrossprod(A, A)))
    all_P[, , i1] <- solve(B, A)
  }

  all_phi <- array(dim=c(d, d, p, p)) # [ , , i, j] for phi_{i, j}
  all_phi_star <- array(dim=c(d, d, p, p)) # [ , , i, j] for phi_{i, j}*

  # Set initial values
  L <- L_star <- Sigma <- Sigma_star <- Gamma <- Id

  # Recursion algorithm (Ansley and Kohn 1986, lemma 2.1)
  for(s in 0:(p - 1)) {
    all_phi[, , s+1, s+1] <- L%*%all_P[, , s+1]%*%solve(L_star)
    all_phi_star[, , s+1, s+1] <- tcrossprod(L_star, all_P[, , s+1])%*%solve(L)

    if(s >= 1) {
      for(k in 1:s) {
        all_phi[, , s+1, k] <- all_phi[, , s, k] - all_phi[, , s+1, s+1]%*%all_phi_star[, , s, s-k+1]
        all_phi_star[, , s+1, k] <- all_phi_star[, , s, k] - all_phi_star[, , s+1, s+1]%*%all_phi[, , s, s-k+1]
      }
    }

    if(s < p - 1) { # These are not needed in the last round because only coefficient matrices will be returned.
      Sigma_next <- Sigma - all_phi[, , s+1, s+1]%*%tcrossprod(Sigma_star, all_phi[, , s+1, s+1])
      if(s < p + 1) {
        Sigma_star <- Sigma_star - all_phi_star[, , s+1, s+1]%*%tcrossprod(Sigma, all_phi_star[, , s+1, s+1])
        L_star <- t(chol(Sigma_star))
      }
      Sigma <- Sigma_next
      L <- t(chol(Sigma))
    }
  }
  as.vector(all_phi[, , p, 1:p])
}


#' @title Create random VAR model error term covariance matrix
#'
#' @description \code{random_covmat} generates random VAR model \eqn{(dxd)} error term covariance matrix \eqn{\Omega}
#'   from (scaled) Wishart distribution.
#'
#' @inheritParams loglikelihood
#' @inheritParams GAfit
#' @return Returns a \eqn{(d(d+1)/2x1)} vector containing vech-vectorized covariance matrix
#'       \eqn{\Omega}.
#' @keywords internal

random_covmat <- function(d, omega_scale) {
  smart_covmat(d=d, Omega=diag(x=omega_scale), accuracy=1)
}


#' @title Create random VAR model \eqn{(dxd)} error term covariance matrix \eqn{\Omega}
#'   fairly close to the given \strong{positive definite} covariance matrix using (scaled)
#'   Wishart distribution
#'
#' @description \code{smart_covmat} generates random VAR model \eqn{(dxd)} error term covariance matrix \eqn{\Omega}
#'   from (scaled) Wishart distribution that is fairly close to the given matrix.
#'
#' @inheritParams loglikelihood
#' @param Omega a symmetric positive definite \eqn{(dxd)} covariance matrix specifying
#'   expected value of the matrix to be generated.
#' @param accuracy a positive real number adjusting how close to the given covariance matrix
#'   the returned individual should be.
#'
#'   The standard deviation of each diagonal element is...
#'   \itemize{
#'     \item \eqn{\omega_{i,i}/}\code{accuracy} when \code{accuracy > d/2}
#'     \item and \code{sqrt(2/d)*}\eqn{\omega_{i,i}} when \code{accuracy <= d/2}.
#'   }
#'   Wishart distribution is used for reduced form models, but for more details read the source code.
#' @inherit random_covmat return
#' @keywords internal

smart_covmat <- function(d, Omega, accuracy) {
  if(accuracy <= d/2) {
    covmat <- rWishart(n=1, df=d, Sigma=Omega/d)[, , 1]
  } else {
    covmat <- (1 - d/(2*accuracy))*Omega + rWishart(n=1, df=(d^2)/2, Sigma=Omega/(d*accuracy))[, , 1]
  }
  vech(covmat)
}


#' @title Create random VAR model impact matrix
#'
#' @description \code{random_impactmat} generates random VAR model \eqn{(dxd)} impact matrix \eqn{B}
#'   with its elements drawn from specific normal distributions (see the source code). If not the first
#'   regime, will create the matrix \eqn{B_m*}.
#'
#' @inheritParams loglikelihood
#' @inheritParams GAfit
#' @param is_regime1 is the impact matrix for Regime 1? Regime 1 impact matrix is constrained so the elements
#'   in its first row are in a decreasing ordering and the diagonal elements are strictly positive.
#' @details If the impact matrix is not for Regime 1, will create the matrix \eqn{B_m*}, which is related
#'   to the impact matrix \eqn{B_m} of Regime m as \eqn{B_m* = B_m - B_1}.
#' @return Returns a \eqn{(d^2 \times 1)} vector containing the vectorized impact matrix \eqn{B}.
#' @keywords internal

random_impactmat <- function(d, B_scale, is_regime1=TRUE) {
  new_B <- matrix(nrow=d, ncol=d)
  if(is_regime1) {
    for(i1 in 1:d) {
      new_B[i1,] <- rnorm(d, sd=sqrt(0.95*B_scale[i1]/d)) # Random elements to each row
    }
    return(order_B(new_B)) # First element in each column normalized to positive and columns ordered to a decreasing order
  } else {
    for(i1 in 1:d) {
      new_B[i1,] <- rnorm(d, sd=sqrt(0.05*B_scale[i1]/d)) # Random elements to each row
    }
    return(new_B) # No constraints since not the first impact matrix
  }
}


#' @title Create a random VAR model \eqn{(dxd)} error impact matrix \eqn{B}
#'   fairly close to the given \strong{invertible} impact matrix.
#'
#' @description \code{smart_impactmat} generates a random VAR model \eqn{(dxd)} error impact matrix \eqn{B}
#'   fairly close to the given \strong{invertible} impact matrix.
#'
#' @inheritParams random_impactmat
#' @param B an invertible \eqn{(dxd)} impact matrix specifying
#'   expected value of the matrix to be generated. Should have strictly positive diagonal entries in
#'   a decreasing order.
#' @param accuracy a positive real number adjusting how close to the given impact matrix
#'   the returned individual should be. Larger value implies higher accuracy.
#' @inherit random_impactmat return
#' @keywords internal

smart_impactmat <- function(d, B, accuracy, is_regime1=TRUE) {
  new_B <- matrix(rnorm(d^2, mean=B, sd=pmax(0.2, abs(B))/accuracy), nrow=d)
  if(is_regime1) {
    return(order_B(new_B)) # First element in each column normalized to positive and columns ordered to a decreasing order
  } else {
    return(new_B) # No constraints since not the first impact matrix
  }
}


#' @title Create random distribution parameter values
#'
#' @description \code{random_distpars} generates random distribution parameter values
#'
#' @inheritParams loglikelihood
#' @return Returns a numeric vector ...
#'   \describe{
#'     \item{If \code{cond_dist == "Gaussian"}:}{of length zero.}
#'     \item{If \code{cond_dist == "Student"}:}{of length one containing a df param strictly larger than two.}
#'     \item{If \code{cond_dist == "ind_Student"}:}{of length d containing df params strictly larger than two.}
#'     \item{If \code{cond_dist == "ind_skewed_t"}:}{of length 2d containing df params strictly larger than two in the first d
#'           elements and skewness params strictly between -1 and 1 in the rest d elements.}
#'   }
#' @keywords internal

random_distpars <- function(d, cond_dist) {
  if(cond_dist == "Gaussian") {
    return(numeric(0))
  } else if(cond_dist == "Student") {
    return(2.000001 + rgamma(1, shape=0.3, rate=0.007))
  } else if(cond_dist == "ind_Student") {
    return(2.000001 + rgamma(d, shape=0.3, rate=0.007))
  } else if(cond_dist == "ind_skewed_t") {
    return(c(2.000001 + rgamma(d, shape=0.3, rate=0.007),
             runif(d, min=-0.999, max=0.999)))
  }
}


#' @title Create random distribution parameter values close to given values
#'
#' @description \code{smart_distpars} generates random degrees of freedom parameter values
#'   close to given values.
#'
#' @param distpars the old distribution parameters (of all regimes)
#' @param accuracy a positive real number adjusting how close to the given distribution parameters
#'   the returned values should be.
#' @return Returns a numeric vector ...
#'   \describe{
#'     \item{If \code{cond_dist == "Gaussian"}:}{of length zero.}
#'     \item{If \code{cond_dist == "Student"}:}{of length one containing a degrees of freedom parameter value
#'      (strictly larger than two).}
#'     \item{If \code{cond_dist == "ind_Student"}:}{of length d containing a degrees of freedom parameter values
#'      (strictly larger than two).}
#'     \item{If \code{cond_dist == "ind_skewed_t"}:}{of length 2d containing df params strictly larger than two in the first d
#'           elements and skewness params strictly between -1 and 1 in the rest d elements.}
#'   }
#' @keywords internal

smart_distpars <- function(distpars, accuracy, cond_dist) {
  if(cond_dist == "Gaussian") {
    return(numeric(0))
  } else if(cond_dist == "Student" || cond_dist == "ind_Student") {
    return(pmax(2.01, rnorm(length(distpars), mean=distpars, sd=pmax(0.2, abs(distpars))/accuracy))) # Make sure all df are above 2
  } else if(cond_dist == "ind_skewed_t")
    d <- length(distpars)/2
    return(c(pmax(2.01, rnorm(d, mean=distpars[1:d], sd=pmax(0.2, abs(distpars[1:d]))/accuracy)), # df > 2
             pmax(pmin(rnorm(d, mean=distpars[(d + 1):length(distpars)], sd=pmax(0.1, abs(distpars[(d + 1):length(distpars)]))/accuracy),
                       0.99), -0.99))) # skew in (-1, 1)
}


#' @title Create random transition weight parameter values
#'
#' @description \code{random_weightpars} generates random transition weight parameter values
#'
#' @inheritParams random_ind
#' @return Returns a numeric vector ...
#'   \describe{
#'     \item{If \code{weight_function == "relative_dens"}:}{a length \code{M-1} vector \eqn{(\alpha_1,...,\alpha_{M-1})}.}
#'     \item{If \code{weight_function == "logistic"}:}{a length two vector \eqn{(c,\gamma)},
#'           where \eqn{c\in\mathbb{R}} is the location parameter and \eqn{\gamma >0} is the scale parameter.}
#'     \item{If \code{weight_function == "mlogit"}:}{a length \eqn{((M-1)k\times 1)} vector \eqn{(\gamma_1,...,\gamma_{M-1})},
#'           where \eqn{\gamma_m} \eqn{(k\times 1)}, \eqn{m=1,...,M-1} contains the mlogit-regression coefficients of the \eqn{m}th
#'           regime. Specifically, for switching variables with indices in \eqn{I\subset\lbrace 1,...,d\rbrace}, and with
#'           \eqn{\tilde{p}\in\lbrace 1,...,p\rbrace} lags included, \eqn{\gamma_m} contains the coefficients for the vector
#'           \eqn{z_{t-1} = (1,\tilde{z}_{\min\lbrace I\rbrace},...,\tilde{z}_{\max\lbrace I\rbrace})}, where
#'           \eqn{\tilde{z}_{i} =(y_{it-1},...,y_{it-\tilde{p}})}, \eqn{i\in I}. So \eqn{k=1+|I|\tilde{p}}
#'           where \eqn{|I|} denotes the number of elements in \eqn{I}.}
#'     \item{If \code{weight_function == "exponential"}:}{a length two vector \eqn{(c,\gamma)},
#'           where \eqn{c\in\mathbb{R}} is the location parameter and \eqn{\gamma >0} is the scale parameter.}
#'     \item{If \code{weight_function == "threshold"}:}{a length \eqn{M-1} vector \eqn{(r_1,...,r_{M-1})},
#'           where \eqn{r_1,...,r_{M-1}} are the threshold values in an increasing order.}
#'     \item{If \code{weight_function == "exogenous"}:}{of length zero.}
#'   }
#' @keywords internal

random_weightpars <- function(M, weight_function=c("relative_dens", "logistic", "mlogit", "exponential", "threshold", "exogenous"),
                              weightfun_pars=NULL, AR_constraints=NULL, mean_constraints=NULL,
                              weight_constraints=NULL, weight_scale) {
  weight_function <- match.arg(weight_function)
  if(M == 1 || weight_function == "exogenous") {
    return(numeric(0))
  }
  if(weight_function == "relative_dens") {
    if(is.null(weight_constraints)) {
      alphas <- runif(n=M, min=0.00, max=1)
      # Sort and standardize alphas; don't sort if AR_constraints or mean_constraints are used
      if(is.null(AR_constraints) && is.null(mean_constraints)) {
        alphas <- sort(alphas, decreasing=TRUE)
        alphas <- alphas/sum(alphas)
      }
      ret <- alphas[-M]
    } else {
      if(all(weight_constraints[[1]] == 0)) {
        ret <- numeric(0) # alpha = r constant, so it is not parametrized
      } else {
        ret <- sort(runif(n=ncol(weight_constraints[[1]]), min=0, max=0.8), decreasing=TRUE)
      }
    }
  } else if(weight_function == "logistic" || weight_function == "exponential") {
    if(is.null(weight_constraints)) {
      ret <- c(rnorm(n=1, mean=weight_scale[1], sd=weight_scale[2]), abs(rnorm(n=1, mean=0, sd=weight_scale[3])) + 1e-8)
    } else {
      if(all(weight_constraints[[1]] == 0)) {
        ret <- numeric(0) # alpha = r constant, so it is not parametrized
      } else {
        ret <- rnorm(n=ncol(weight_constraints[[1]]), mean=0, sd=weight_scale[3])
      }
    }
  } else if(weight_function == "mlogit") {
    if(is.null(weight_constraints)) {
      k <- 1 + length(weightfun_pars[[1]])*weightfun_pars[[2]] # how many pars in each gamma_m
      ret <- rnorm(n=(M - 1)*k, mean=0, sd=weight_scale[3])
      # Replace different coefficients for constant terms:
      ret[(1:(M - 1) - 1)*k + 1] <- rnorm(n=(M - 1), mean=weight_scale[1], sd=weight_scale[2])
    } else {
      if(all(weight_constraints[[1]] == 0)) {
        ret <- numeric(0) # alpha = r constant, so it is not parametrized
      } else {
        ret <- rnorm(n=ncol(weight_constraints[[1]]), mean=0, sd=weight_scale[3])
      }
    }
  } else if(weight_function == "threshold") {
    if(is.null(weight_constraints)) {
      ret <- sort(runif(n=M-1, min=weight_scale[1], max=weight_scale[2]), decreasing=FALSE)
    } else {
      if(all(weight_constraints[[1]] == 0)) {
        ret <- numeric(0) # alpha = r constant, so it is not parametrized
      } else {
        # We don't sort here so that we don't accidentally always be outside the parameter space with some weird weight constraints
        ret <- runif(n=ncol(weight_constraints[[1]]), min=weight_scale[1], max=weight_scale[2])
      }
    }
  }
  ret
}

#' @title Create random transition weight parameter values
#'
#' @description \code{smart_weightpars} generates random transition weight parameter values
#'  relatively close to the ones given in \code{weight_pars}
#'
#' @inheritParams smart_ind
#' @param weight_pars a vector containing transition weight parameter values.
#'   \describe{
#'     \item{If \code{weight_function == "relative_dens"}:}{a length \code{M-1} vector \eqn{(\alpha_1,...,\alpha_{M-1})}.}
#'     \item{If \code{weight_function == "logistic"}:}{a length two vector \eqn{(c,\gamma)},
#'           where \eqn{c\in\mathbb{R}} is the location parameter and \eqn{\gamma >0} is the scale parameter.}
#'     \item{If \code{weight_function == "mlogit"}:}{a length \eqn{((M-1)k\times 1)} vector \eqn{(\gamma_1,...,\gamma_{M-1})},
#'           where \eqn{\gamma_m} \eqn{(k\times 1)}, \eqn{m=1,...,M-1} contains the mlogit-regression coefficients of the \eqn{m}th
#'           regime. Specifically, for switching variables with indices in \eqn{I\subset\lbrace 1,...,d\rbrace}, and with
#'           \eqn{\tilde{p}\in\lbrace 1,...,p\rbrace} lags included, \eqn{\gamma_m} contains the coefficients for the vector
#'           \eqn{z_{t-1} = (1,\tilde{z}_{\min\lbrace I\rbrace},...,\tilde{z}_{\max\lbrace I\rbrace})}, where
#'           \eqn{\tilde{z}_{i} =(y_{it-1},...,y_{it-\tilde{p}})}, \eqn{i\in I}. So \eqn{k=1+|I|\tilde{p}}
#'           where \eqn{|I|} denotes the number of elements in \eqn{I}.}
#'     \item{If \code{weight_function == "exponential"}:}{a length two vector \eqn{(c,\gamma)},
#'           where \eqn{c\in\mathbb{R}} is the location parameter and \eqn{\gamma >0} is the scale parameter.}
#'     \item{If \code{weight_function == "threshold"}:}{a length \eqn{M-1} vector \eqn{(r_1,...,r_{M-1})},
#'           where \eqn{r_1,...,r_{M-1}} are the threshold values in an increasing order.}
#'     \item{If \code{weight_function == "exogenous"}:}{of length zero.}
#'   }
#' @inherit random_weightpars return
#' @keywords internal

smart_weightpars <- function(M, weight_pars,
                             weight_function=c("relative_dens", "logistic", "mlogit", "exponential", "threshold", "exogenous"),
                             weight_constraints=NULL, accuracy) {
  weight_function <- match.arg(weight_function)
  if(M == 1 || weight_function == "exogenous") {
    return(numeric(0))
  }
  if(!is.null(weight_constraints)) {
    if(all(weight_constraints[[1]] == 0)) {
      return(numeric(0)) # alpha = r constant, so it is not parametrized
    }
  }
  if(weight_function == "relative_dens") {
    # Note: this assumes that the unparametrized alpha is not in weight_pars!
    weight_pars <- abs(rnorm(n=length(weight_pars) + 1, mean=c(weight_pars, 1 - sum(weight_pars)),
                         sd=pmax(0.2, c(weight_pars, 1 - sum(weight_pars)))/accuracy))
    ret <- (weight_pars/sum(weight_pars))[-M] # Standardize alphas; sorts in GA in sort_regimes if no constraints are imposed
  } else if(weight_function == "mlogit" || weight_function == "logistic" || weight_function == "exponential") {
    ret <- rnorm(n=length(weight_pars), mean=weight_pars, sd=pmax(0.2, weight_pars)/accuracy)
  } else if(weight_function == "threshold") {
    # Threshold pars always need to be sorted;
    ret <- sort(rnorm(n=length(weight_pars), mean=weight_pars, sd=pmax(0.2, weight_pars)/accuracy), decreasing=FALSE)
  }
  ret
}



#' @title Create random mean parametrized parameter vector
#'
#' @description \code{random_ind} generates random mean parametrized parameter vector.
#'
#' @inheritParams GAfit
#' @inheritParams loglikelihood
#' @param force_stability Should the algorithm proposed by Ansley and Kohn (1986) be used to generate
#'   AR matrices that always satisfy the stability condition? Not supported if AR constraints are
#'   employed.
#' @details Structural models are not supported!
#' @return Returns random mean parametrized parameter vector that has the same form as the argument \code{params}
#'   in the other functions, for instance, in the function \code{loglikelihood}.
#' @references
#'  \itemize{
#'    \item Ansley C.F., Kohn R. 1986. A note on reparameterizing a vector autoregressive
#'       moving average model to enforce stationarity.
#'       \emph{Journal of statistical computation and simulation}, \strong{24}:2, 99-106.
#'  }
#' @keywords internal

random_ind <- function(p, M, d, weight_function=c("relative_dens", "logistic", "mlogit", "exponential", "threshold", "exogenous"),
                       weightfun_pars=NULL, cond_dist=c("Gaussian", "Student", "ind_Student", "ind_skewed_t"),
                       AR_constraints=NULL, mean_constraints=NULL, weight_constraints=NULL, force_stability=is.null(AR_constraints),
                       mu_scale, mu_scale2, omega_scale, B_scale, weight_scale, ar_scale=1, ar_scale2=1, fixed_params=NULL) {
  weight_function <- match.arg(weight_function)
  cond_dist <- match.arg(cond_dist)
  g <- ifelse(is.null(mean_constraints), M, length(mean_constraints)) # Number of groups of regimes with the same mean parameters

  # Generate mean params
  if(is.null(fixed_params)) {
    if(is.null(mean_constraints)) {
      mean_pars <- as.vector(replicate(n=M, expr=rnorm(d, mean=mu_scale, sd=mu_scale2)))
    } else { # mean_constraints used
      mean_pars <- rnorm(d*g, mean=mu_scale, sd=mu_scale2)
    }
  } else {
    # Obtain the fixed mean parameters
    mean_pars <- fixed_params[1:(d*M)]
  }


  # Generate AR params
  if(is.null(fixed_params)) {
    if(is.null(AR_constraints) && force_stability) {
      coefmat_pars <- as.vector(replicate(n=M, random_coefmats2(p=p, d=d, ar_scale=ar_scale)))
    } else {
      scale_A <- ar_scale2*ifelse(is.null(AR_constraints),
                                  1 + log(2*mean(c((p - 0.2)^(1.25), d))),
                                  1 + (sum(AR_constraints)/(M*d^2))^0.85)
      if(is.null(AR_constraints)) {
        coefmat_pars <- as.vector(replicate(n=M, expr=random_coefmats(d=d, how_many=p, scale=scale_A)))
      } else { # AR_constraints employed
        coefmat_pars <- rnorm(ncol(AR_constraints), mean=0, sd=0.5/scale_A) # random psi
      }
    }
  } else {
    # Obtain the fixed parameters
    if(is.null(AR_constraints)) {
      coefmat_pars <- fixed_params[(d*M + 1):(d*M + M*p*d^2)]
      q <- M*p*d^2 # q is used later in the code
    } else {
      q <- ncol(AR_constraints)
      coefmat_pars <- fixed_params[(d*M + 1):(d*M + q)]
    }
  }

  # Generate covmat params
  if(cond_dist == "ind_Student" || cond_dist == "ind_skewed_t") { # Covmat pars impact matrix params
    if(M == 1) {
      covmat_pars <- as.vector(random_impactmat(d=d, B_scale=B_scale, is_regime1=TRUE))
    } else {
      covmat_pars <- c(random_impactmat(d=d, B_scale=B_scale, is_regime1=TRUE), # Regime 1 impact matrix is constrained
                       replicate(n=M-1, expr=random_impactmat(d=d, B_scale=B_scale, is_regime1=FALSE))) # Regime 2,...,M impact matrices
    }
  } else { # cond_dist == "Gaussian" or "Student"
    covmat_pars <- as.vector(replicate(n=M, expr=random_covmat(d=d, omega_scale=omega_scale)))
  }

  # Generate weight params
  if(M > 1 && weight_function != "exogenous") {
    if(is.null(fixed_params)) {
      weight_pars <- random_weightpars(M=M, weight_function=weight_function, weightfun_pars=weightfun_pars,
                                       AR_constraints=AR_constraints, mean_constraints=mean_constraints,
                                       weight_constraints=weight_constraints, weight_scale=weight_scale)
    } else {
      # Obtain the fixed parameters
      if(is.null(weight_constraints)) {
        weight_pars <- fixed_params[(d*M + q + 1):length(fixed_params)]
      } else {
        weight_pars <- numeric(0) # Only allowed weight constraint here fixes the weight parameters (checked in GAfit)
      }
    }
  } else {
    weight_pars <- numeric(0) # Replicate returns a list if the value is numeric(0)
  }

  # Generate distribution params (df etc)
  dist_pars <- random_distpars(d=d, cond_dist=cond_dist)

  # Return the parameter vector
  c(mean_pars, coefmat_pars, covmat_pars, weight_pars, dist_pars)
}


#' @title Create random parameter vector that is fairly close to a given parameter vector
#'
#' @description \code{smart_ind} creates random mean parametrized parameter vector that is
#'   model fairly close to a given parameter vector. The result may not be satisfy the stability
#'   condition.
#'
#' @inheritParams loglikelihood
#' @inheritParams GAfit
#' @inheritParams random_coefmats2
#' @param accuracy a positive real number adjusting how close to the given parameter vector the returned individual should be.
#'   Larger number means larger accuracy. Read the source code for details.
#' @param which_random a vector with length between 1 and M specifying the mixture components that should be random instead of
#'   close to the given parameter vector. This does not consider constrained AR or lambda parameters.
#' @details Structural models are not supported!
#' @inherit random_ind return references
#' @keywords internal

smart_ind <- function(p, M, d, params,
                      weight_function=c("relative_dens", "logistic", "mlogit", "exponential", "threshold", "exogenous"),
                      weightfun_pars=NULL, cond_dist=c("Gaussian", "Student", "ind_Student", "ind_skewed_t"),
                      AR_constraints=NULL, mean_constraints=NULL, weight_constraints=NULL, accuracy=1,
                      which_random=numeric(0), mu_scale, mu_scale2, omega_scale, B_scale, ar_scale=1, ar_scale2=1,
                      fixed_params=NULL) {
  weight_function <- match.arg(weight_function)
  cond_dist <- match.arg(cond_dist)
  scale_A <- ar_scale2*(1 + log(2*mean(c((p - 0.2)^(1.25), d))))
  params_std <- reform_constrained_pars(p=p, M=M, d=d, params=params, weight_function=weight_function, weightfun_pars=weightfun_pars,
                                        cond_dist=cond_dist, identification="reduced_form", AR_constraints=AR_constraints,
                                        mean_constraints=mean_constraints, weight_constraints=weight_constraints,
                                        B_constraints=NULL) # Used so that pick_pars-functions works
  dist_pars <- pick_distpars(d=d, params=params_std, cond_dist=cond_dist)
  all_Omega <- pick_Omegas(p=p, M=M, d=d, params=params_std, cond_dist=cond_dist,
                           identification="reduced_form") # Impact matrices for cond_dist == "ind_Student"
  new_pars <- numeric(length(params))
  if(is.null(AR_constraints) && is.null(mean_constraints) && is.null(weight_constraints)) {
    if(is.null(fixed_params)) {
      all_means_and_A <- params[1:(d*M + M*p*d^2)] # all mu + A if called from GAfit
      new_pars[1:(d*M + M*p*d^2)] <- rnorm(n=length(all_means_and_A), # Fills in smart means and A, later replaced with random if random regime
                                           mean=all_means_and_A, sd=pmax(0.2, abs(all_means_and_A))/accuracy)
    } else { # Fixed mean and AR parameters
      new_pars[1:(d*M + M*p*d^2)] <- fixed_params[1:(d*M + M*p*d^2)] # Obtain the fixed parameters
    }
    for(m in 1:M) {
      if(any(which_random) == m) { # Not a smart regime
        if(is.null(fixed_params)) { # No random means or AR mats with fixed parameters
          new_pars[((m - 1)*d + 1):(m*d)] <- rnorm(d, mean=mu_scale, sd=mu_scale2) # Random mean
          if(runif(1) > 0.5) {
            new_pars[(M*d + (m - 1)*p*d^2 + 1):(M*d + m*p*d^2)] <- random_coefmats(d=d, how_many=p, scale=scale_A) # Without algorithm
          } else {
            new_pars[(M*d + (m - 1)*p*d^2 + 1):(M*d + m*p*d^2)] <- random_coefmats2(p=p, d=d, ar_scale=ar_scale) # Use the algorithm
          }
        }
        if(cond_dist == "ind_Student" || cond_dist == "ind_skewed_t") { # Impact matrix parameters
          new_pars[(M*d + M*p*d^2 + (m - 1)*d^2 + 1):(M*d + M*p*d^2 + m*d^2)] <- random_impactmat(d=d, B_scale=B_scale, is_regime1=(m == 1))
        } else { # cond_dist == "Gaussian" or "Student", cov mat parameters
          new_pars[(M*d + M*p*d^2 + (m - 1)*d*(d + 1)/2 + 1):(M*d + M*p*d^2 + m*d*(d + 1)/2)] <- random_covmat(d=d, omega_scale=omega_scale)
        }
      } else { # Smart_regime, smart mean and AR parameters already generated
        if(cond_dist == "ind_Student" || cond_dist == "ind_skewed_t") {  # Impact matrix parameters
          new_pars[(M*d + M*p*d^2 + (m - 1)*d^2 + 1):(M*d + M*p*d^2 + m*d^2)] <- smart_impactmat(d=d, B=all_Omega[, , m],
                                                                                                 accuracy=accuracy,
                                                                                                 is_regime1=(m == 1))
        } else { # cond_dist == "Gaussian" or "Student", cov mat parameters
          new_pars[(M*d + M*p*d^2 + (m - 1)*d*(d + 1)/2 + 1):(M*d + M*p*d^2 + m*d*(d + 1)/2)] <- smart_covmat(d=d, Omega=all_Omega[, , m],
                                                                                                              accuracy=accuracy)
        }
      }
    }
    if(M > 1 && weight_function != "exogenous") {
      if(cond_dist == "ind_Student" || cond_dist == "ind_skewed_t") {
        n_covmat_pars <- M*d^2
      } else {
        n_covmat_pars <- M*d*(d + 1)/2
      }
      if(is.null(fixed_params)) {
        weight_pars <- pick_weightpars(p=p, M=M, d=d, params=params, weight_function=weight_function, weightfun_pars=weightfun_pars,
                                       cond_dist=cond_dist)
        if(weight_function == "relative_dens") {
          weight_pars <- weight_pars[-length(weight_pars)] # Remove alpha_M
        }
        new_pars[(M*d + M*p*d^2 + n_covmat_pars + 1):
                   (M*d + M*p*d^2 + n_covmat_pars + length(weight_pars))] <- smart_weightpars(M=M,
                                                                                              weight_pars=weight_pars,
                                                                                              weight_function=weight_function,
                                                                                              weight_constraints=weight_constraints,
                                                                                              accuracy=accuracy)
      } else { # Fixed weight parameters
        n_weight_params <- length(fixed_params) - M*d - M*p*d^2
        new_pars[(M*d + M*p*d^2 + n_covmat_pars + 1):
                   (M*d + M*p*d^2 + n_covmat_pars + n_weight_params)] <- fixed_params[(M*d + M*p*d^2 + 1):length(fixed_params)]
      }

    }
  } else { # AR, mean, or weight constraints employed
    # mean parameters
    g <- ifelse(is.null(mean_constraints), M, length(mean_constraints)) # Number of groups of regimes with the same mean parameters
    if(length(which_random) == 0) {
      smart_regs <- 1:M
    } else {
      smart_regs <- (1:M)[-which_random]
    }
    if(is.null(fixed_params)) {
      all_means <- matrix(params[1:(d*g)], nrow=d, ncol=g, byrow=FALSE)
      mean_pars <- as.vector(vapply(1:g, function(m) {
        which_reg <- ifelse(is.null(mean_constraints), m, mean_constraints[[m]]) # Can be many if mean_constraints used
        if(any(which_reg %in% smart_regs)) { # Smart parameters
          rnorm(d, mean=all_means[,m], sd=abs(all_means[,m]/accuracy))
        } else { # Random parameters
          rnorm(d, mean=mu_scale, sd=mu_scale2)
        }
      }, numeric(d)))
    } else { # mean parameters are fixed, no mean constraints (checked in GAfit)
      mean_pars <- fixed_params[1:(d*M)]
    }

    # AR parameters
    if(is.null(AR_constraints)) {
      q <- M*p*d^2
      if(is.null(fixed_params)) {
        all_A <- pick_allA(p=p, M=M, d=d, params=params_std)
        AR_pars <- vapply(1:M, function(m) {
          if(any(which_random) == m) { # Random AR matrix
            if(runif(1) > 0.5) { # Use algorithm to force stability of coefficient matrices
              random_coefmats2(p=p, d=d, ar_scale=ar_scale)
            } else {
              random_coefmats(d=d, how_many=p, scale=scale_A)
            }
          } else { # Smart AR matrix
            all_Am <- as.vector(all_A[, , , m])
            rnorm(n=length(all_Am), mean=all_Am, sd=pmax(0.2, abs(all_Am))/accuracy)
          }
        }, numeric(p*d^2))
      } else { # AR parameters are fixed
        AR_pars <- fixed_params[(d*M + 1):(d*M + q)]
      }
    } else {
      q <- ncol(AR_constraints)
      if(is.null(fixed_params)) {
        psi <- params[(g*d + 1):(g*d + q)]
        AR_pars <- rnorm(q, mean=psi, sd=pmax(0.2, abs(psi))/accuracy)
      } else { # AR parameters are fixed
        AR_pars <- fixed_params[(d*M + 1):(d*M + q)]
      }
    }

    # Covariance matrix / impact matrix parameters
    if(cond_dist == "ind_Student" || cond_dist == "ind_skewed_t") { # impact mat params
      covmat_pars <- as.vector(vapply(1:M, function(m) {
        if(any(which_random == m)) {
          random_impactmat(d=d, B_scale=B_scale, is_regime1=(m == 1))
        } else {
          smart_impactmat(d=d, B=all_Omega[, , m], accuracy=accuracy, is_regime1=(m == 1))
        }
      }, numeric(d^2)))
    } else { # cond_dist == "Gaussian" or "Student", cov mat params
      covmat_pars <- as.vector(vapply(1:M, function(m) {
        if(any(which_random == m)) {
          random_covmat(d=d, omega_scale=omega_scale)
        } else {
          smart_covmat(d=d, Omega=all_Omega[, , m], accuracy=accuracy)
        }
      }, numeric(d*(d + 1)/2)))
    }

    # Weight function parameters
    if(M > 1 && weight_function != "exogenous") {
      if(is.null(fixed_params)) {
        if(is.null(weight_constraints)) {
          weight_pars <- pick_weightpars(p=p, M=M, d=d, params=params_std, weight_function=weight_function, weightfun_pars=weightfun_pars,
                                         cond_dist=cond_dist)
          if(weight_function == "relative_dens") {
            weight_pars <- weight_pars[-length(weight_pars)]
          }
          weight_pars <- smart_weightpars(M=M, weight_pars=weight_pars, weight_function=weight_function,
                                          weight_constraints=weight_constraints, accuracy=accuracy)
        } else { # Weight constraints used
          if(all(weight_constraints[[1]] == 0)) {
            weight_pars <- numeric(0) # alpha = r known constant so it is not parametrized here
          } else {
            weight_pars <- smart_weightpars(M=M,
                                            weight_pars=params[(M*g + q + M*d*(d + 1)/2 + 1):(M*g + q + M*d*(d + 1)/2 +
                                                                                                ncol(weight_constraints[[1]]))],
                                            weight_function=weight_function,
                                            weight_constraints=weight_constraints, accuracy=accuracy)
          }
        }
      } else { # weight parameters are fixed
        if(is.null(weight_constraints)) {
          n_weight_params <- length(fixed_params) - M*d - q
          weight_pars <- fixed_params[(M*d + q + 1):(M*d + q + n_weight_params)]
        } else {
          weight_pars <- numeric(0) # The only allowed weight constraint here fixes the weight parameters (checked in GAfit)
        }
      }

    } else {
      weight_pars <- numeric(0)
    }
    new_pars[1:(d*g + q + length(covmat_pars) + length(weight_pars))] <- c(mean_pars, AR_pars, covmat_pars, weight_pars)
  }
  if(cond_dist == "Student") {
    # Add degrees of freedom parameter
    new_pars[length(new_pars)] <- smart_distpars(distpars=dist_pars, accuracy=accuracy, cond_dist=cond_dist)
  } else if(cond_dist == "ind_Student") {
    # Add degrees of freedom parameters
    new_pars[(length(new_pars) - d + 1):length(new_pars)] <- smart_distpars(distpars=dist_pars, accuracy=accuracy, cond_dist=cond_dist)
  } else if(cond_dist == "ind_skewed_t") {
    # Add degrees of freedom and skewness parameters
    new_pars[(length(new_pars) - 2*d + 1):length(new_pars)] <- smart_distpars(distpars=dist_pars, accuracy=accuracy, cond_dist=cond_dist)
  }
  new_pars
}

Try the sstvars package in your browser

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

sstvars documentation built on April 11, 2025, 5:47 p.m.