class_docstr: Parameters
  ----------
  regressor_col : list
      Names of regressor columns, if any
  regressor_sign :  list
      list with values { '+', '-', '=' } such that
      '+' indicates regressor coefficient estimates are constrained to [0, inf).
      '-' indicates regressor coefficient estimates are constrained to (-inf, 0].
      '=' indicates regressor coefficient estimates can be any value between (-inf, inf).
      The length of `regressor_sign` must be the same length as `regressor_col`. If None,
      all elements of list will be set to '='.
  regressor_beta_prior : list
      list of prior float values for regressor coefficient betas. The length of `regressor_beta_prior`
      must be the same length as `regressor_col`. If None, use non-informative priors.
  regressor_sigma_prior : list
      list of prior float values for regressor coefficient sigmas. The length of `regressor_sigma_prior`
      must be the same length as `regressor_col`. If None, use non-informative priors.
  regression_penalty : { 'fixed_ridge', 'lasso', 'auto_ridge' }
      regression penalty method
  lasso_scale : float
      float value between [0, 1], applicable only if `regression_penalty` == 'lasso'
  auto_ridge_scale : float
      float value between [0, 1], applicable only if `regression_penalty` == 'auto_ridge'
  slope_sm_input : float
      float value between [0, 1]. A larger value puts more weight on the current slope.
      If None, the model will estimate this value.
  period : int
      Used to set `time_delta` as `1 / max(period, seasonality)`. If None and no seasonality,
      then `time_delta` == 1
  damped_factor : float
      Hyperparameter float value between [0, 1]. A smaller value further dampens the previous
      global trend value. Default, 0.8
  global_trend_option : { 'flat', 'linear', 'loglinear', 'logistic' }
      Transformation function for the shape of the forecasted global trend.
  global_trend_sigma_prior : sigma prior of the global trend; default uses 1 standard deviation of response
class_docstr_tok: ["Parameters","--","--","--","--","--","regressor_col",":","list","Names","of","regressor","columns",",","if","any","regressor_sign",":","list","list","with","values","{","'+","'",",","'-","'",",","'=","'","}","such","that","'+","'","indicates","regressor","coefficient","estimates","are","constrained","to","[","0",",","inf",")",".","'-","'","indicates","regressor","coefficient","estimates","are","constrained","to","(","-inf",",","0","]",".","'=","'","indicates","regressor","coefficient","estimates","can","be","any","value","between","(","-inf",",","inf",")",".","The","length","of","`","regressor_sign","`","must","be","the","same","length","as","`","regressor_col","`",".","If","None",",","all","elements","of","list","will","be","set","to","'=","'",".","regressor_beta_prior",":","list","list","of","prior","float","values","for","regressor","coefficient","betas",".","The","length","of","`","regressor_beta_prior","`","must","be","the","same","length","as","`","regressor_col","`",".","If","None",",","use","non-informative","priors",".","regressor_sigma_prior",":","list","list","of","prior","float","values","for","regressor","coefficient","sigmas",".","The","length","of","`","regressor_sigma_prior","`","must","be","the","same","length","as","`","regressor_col","`",".","If","None",",","use","non-informative","priors",".","regression_penalty",":","{","'fixed_ridge","'",",","'lasso","'",",","'auto_ridge","'","}","regression","penalty","method","lasso_scale",":","float","float","value","between","[","0",",","1","]",",","applicable","only","if","`","regression_penalty","`","==","'lasso'","auto_ridge_scale",":","float","float","value","between","[","0",",","1","]",",","applicable","only","if","`","regression_penalty","`","==","'auto_ridge'","slope_sm_input",":","float","float","value","between","[","0",",","1","]",".","A","larger","value","puts","more","weight","on","the","current","slope",".","If","None",",","the","model","will","estimate","this","value",".","period",":","int","Used","to","set","`","time_delta","`","as","`","1","\/","max","(","period",",","seasonality",")","`",".","If","None","and","no","seasonality",",","then","`","time_delta","`","==","1","damped_factor",":","float","Hyperparameter","float","value","between","[","0",",","1","]",".","A","smaller","value","further","dampens","the","previous","global","trend","value",".","Default",",","0.8","global_trend_option",":","{","'flat","'",",","'linear","'",",","'loglinear","'",",","'logistic","'","}","Transformation","function","for","the","shape","of","the","forecasted","global","trend",".","global_trend_sigma_prior",":","sigma","prior","of","the","global","trend",";","default","uses","1","standard","deviation","of","response"]
docstr: Vectorized version of prediction math
code: def predict(
      self,
      posterior_estimates,
      df,
      training_meta,
      prediction_meta,
      include_error=False,
      **kwargs,
  ):
      """Vectorized version of prediction math"""
      ################################################################
      # Prediction Attributes
      ################################################################
      # n_forecast_steps = prediction_meta[PredictionMetaKeys.FUTURE_STEPS.value]
      start = prediction_meta[PredictionMetaKeys.START_INDEX.value]
      trained_len = training_meta[TrainingMetaKeys.NUM_OF_OBS.value]
      output_len = prediction_meta[
          PredictionMetaKeys.PREDICTION_DF_LEN.value
      ]
      # this is only used in trend and seasonality generation
      full_len = prediction_meta[PredictionMetaKeys.END_INDEX.value]
  
      ################################################################
      # Model Attributes
      ################################################################
      model = deepcopy(posterior_estimates)
      for k, v in model.items():
          model[k] = torch.from_numpy(v)
  
      # We can pull any arbitrary value from teh dictionary because we hold the
      # safe assumption: the length of the first dimension is always the number of samples
      # thus can be safely used to determine `num_sample`. If predict_method is anything
      # other than full, the value here should be 1
      arbitrary_posterior_value = list(model.values())[0]
      num_sample = arbitrary_posterior_value.shape[0]
  
      # seasonality components
      seasonality_levels = model.get(
          SeasonalitySamplingParameters.SEASONALITY_LEVELS.value
      )
      seasonality_smoothing_factor = model.get(
          SeasonalitySamplingParameters.SEASONALITY_SMOOTHING_FACTOR.value
      )
  
      # trend components
      slope_smoothing_factor = model.get(
          BaseSamplingParameters.SLOPE_SMOOTHING_FACTOR.value
      )
      level_smoothing_factor = model.get(
          BaseSamplingParameters.LEVEL_SMOOTHING_FACTOR.value
      )
      local_trend_levels = model.get(
          BaseSamplingParameters.LOCAL_TREND_LEVELS.value
      )
      local_trend_slopes = model.get(
          BaseSamplingParameters.LOCAL_TREND_SLOPES.value
      )
      local_trend = model.get(BaseSamplingParameters.LOCAL_TREND.value)
      residual_degree_of_freedom = model.get(
          BaseSamplingParameters.RESIDUAL_DEGREE_OF_FREEDOM.value
      )
      residual_sigma = model.get(
          BaseSamplingParameters.RESIDUAL_SIGMA.value
      )
  
      # set an additional attribute for damped factor when it is fixed
      # get it through user input field
      damped_factor = torch.empty(num_sample, dtype=torch.double)
      damped_factor.fill_(self.damped_factor)
  
      if self._global_trend_option != GlobalTrendOption.flat.value:
          global_trend_level = model.get(
              GlobalTrendSamplingParameters.GLOBAL_TREND_LEVEL.value
          ).view(
              num_sample,
          )
          global_trend_slope = model.get(
              GlobalTrendSamplingParameters.GLOBAL_TREND_SLOPE.value
          ).view(
              num_sample,
          )
          global_trend = model.get(
              GlobalTrendSamplingParameters.GLOBAL_TREND.value
          )
      else:
          global_trend_level = model.get(
              GlobalTrendSamplingParameters.GLOBAL_TREND_LEVEL.value
          ).view(
              num_sample,
          )
          global_trend = model.get(
              GlobalTrendSamplingParameters.GLOBAL_TREND.value
          )
  
      # regression components
      regressor_beta = model.get(
          RegressionSamplingParameters.REGRESSION_COEFFICIENTS.value
      )
  
      ################################################################
      # Regression Component
      ################################################################
      # calculate regression component
      if self.regressor_col is not None and len(self.regressor_col) > 0:
          regressor_matrix = df[self._regressor_col].values
          if not np.all(np.isfinite(regressor_matrix)):
              raise PredictionException(
                  "Invalid regressors values. They must be all not missing and finite."
              )
          regressor_beta = regressor_beta.t()
          if len(regressor_beta.shape) == 1:
              regressor_beta = regressor_beta.unsqueeze(0)
          regressor_torch = torch.from_numpy(regressor_matrix).double()
          regression = torch.matmul(regressor_torch, regressor_beta)
          regression = regression.t()
      else:
          # regressor is always dependent with df. hence, no need to make full size
          regression = torch.zeros(
              (num_sample, output_len), dtype=torch.double
          )
  
      ################################################################
      # Seasonality Component
      ################################################################
  
      # calculate seasonality component
      if self._seasonality > 1:
          if full_len <= seasonality_levels.shape[1]:
              seasonal_component = seasonality_levels[:, :full_len]
          else:
              seasonality_forecast_length = (
                  full_len - seasonality_levels.shape[1]
              )
              seasonality_forecast_matrix = torch.zeros(
                  (num_sample, seasonality_forecast_length),
                  dtype=torch.double,
              )
              seasonal_component = torch.cat(
                  (seasonality_levels, seasonality_forecast_matrix),
                  dim=1,
              )
      else:
          seasonal_component = torch.zeros(
              (num_sample, full_len), dtype=torch.double
          )
  
      ################################################################
      # Trend Component
      ################################################################
  
      # calculate level component.
      # However, if predicted end of period > training period, update with out-of-samples forecast
      if full_len <= trained_len:
          full_local_trend = local_trend[:, :full_len]
          full_global_trend = global_trend[:, :full_len]
  
          # in-sample error are iids
          if include_error:
              error_value = nct.rvs(
                  df=residual_degree_of_freedom.unsqueeze(-1),
                  nc=0,
                  loc=0,
                  scale=residual_sigma.unsqueeze(-1),
                  size=(num_sample, full_len),
              )
  
              error_value = torch.from_numpy(
                  error_value.reshape(num_sample, full_len)
              ).double()
              full_local_trend += error_value
      else:
          trend_forecast_length = full_len - trained_len
          trend_forecast_init = torch.zeros(
              (num_sample, trend_forecast_length), dtype=torch.double
          )
          full_local_trend = local_trend[:, :full_len]
          # for convenience, we lump error on local trend since the formula would
          # yield the same as yhat + noise - global_trend - seasonality - regression
          # equivalent with local_trend + noise
          # in-sample error are iids
          if include_error:
              error_value = nct.rvs(
                  df=residual_degree_of_freedom.unsqueeze(-1),
                  nc=0,
                  loc=0,
                  scale=residual_sigma.unsqueeze(-1),
                  size=(num_sample, full_local_trend.shape[1]),
              )
  
              error_value = torch.from_numpy(
                  error_value.reshape(
                      num_sample, full_local_trend.shape[1]
                  )
              ).double()
              full_local_trend += error_value
  
          full_local_trend = torch.cat(
              (full_local_trend, trend_forecast_init), dim=1
          )
          full_global_trend = torch.cat(
              (global_trend[:, :full_len], trend_forecast_init), dim=1
          )
          last_local_trend_level = local_trend_levels[:, -1]
          last_local_trend_slope = local_trend_slopes[:, -1]
  
          for idx in range(trained_len, full_len):
              # based on model, split cases for trend update
              curr_local_trend = (
                  last_local_trend_level
                  + damped_factor.flatten() * last_local_trend_slope
              )
              full_local_trend[:, idx] = curr_local_trend
              # idx = time - 1
              if (
                  self.global_trend_option
                  == GlobalTrendOption.linear.name
              ):
                  full_global_trend[:, idx] = (
                      global_trend_level
                      + global_trend_slope * idx * self._time_delta
                  )
              elif (
                  self.global_trend_option
                  == GlobalTrendOption.loglinear.name
              ):
                  full_global_trend[
                      :, idx
                  ] = global_trend_level + global_trend_slope * np.log(
                      1 + idx * self._time_delta
                  )
              elif (
                  self.global_trend_option
                  == GlobalTrendOption.logistic.name
              ):
                  full_global_trend[:, idx] = self.global_floor + (
                      self.global_cap - self.global_floor
                  ) / (
                      1
                      + torch.exp(
                          -1
                          * (
                              global_trend_level
                              + global_trend_slope
                              * idx
                              * self._time_delta
                          )
                      )
                  )
              elif (
                  self.global_trend_option
                  == GlobalTrendOption.flat.name
              ):
                  full_global_trend[:, idx] = global_trend_level
  
              if include_error:
                  error_value = nct.rvs(
                      df=residual_degree_of_freedom,
                      nc=0,
                      loc=0,
                      scale=residual_sigma,
                      size=num_sample,
                  )
                  error_value = torch.from_numpy(error_value).double()
                  full_local_trend[:, idx] += error_value
  
              # now full_local_trend contains the error term and hence we need to use
              # curr_local_trend as a proxy of previous level index
              new_local_trend_level = (
                  level_smoothing_factor * full_local_trend[:, idx]
                  + (1 - level_smoothing_factor) * curr_local_trend
              )
              last_local_trend_slope = (
                  slope_smoothing_factor
                  * (new_local_trend_level - last_local_trend_level)
                  + (1 - slope_smoothing_factor)
                  * damped_factor.flatten()
                  * last_local_trend_slope
              )
  
              if (
                  self._seasonality > 1
                  and idx + self._seasonality < full_len
              ):
                  seasonal_component[:, idx + self._seasonality] = (
                      seasonality_smoothing_factor.flatten()
                      * (
                          full_local_trend[:, idx]
                          + seasonal_component[:, idx]
                          - new_local_trend_level
                      )
                      + (1 - seasonality_smoothing_factor.flatten())
                      * seasonal_component[:, idx]
                  )
  
              last_local_trend_level = new_local_trend_level
  
      ################################################################
      # Combine Components
      ################################################################
  
      # trim component with right start index
      trend_component = (
          full_global_trend[:, start:] + full_local_trend[:, start:]
      )
      seasonal_component = seasonal_component[:, start:]
  
      # sum components
      pred_array = trend_component + seasonal_component + regression
  
      pred_array = pred_array.numpy()
      trend_component = trend_component.numpy()
      seasonal_component = seasonal_component.numpy()
      regression = regression.numpy()
  
      out = {
          PredictionKeys.PREDICTION.value: pred_array,
          PredictionKeys.TREND.value: trend_component,
          PredictionKeys.SEASONALITY.value: seasonal_component,
          PredictionKeys.REGRESSION.value: regression,
      }
  
      return out
  
imports: import numpy
  import pandas
  from scipy.stats import nct
  from copy import deepcopy
  import torch
  from enum import Enum
  from ..constants.constants import DEFAULT_REGRESSOR_SIGN, DEFAULT_REGRESSOR_BETA, DEFAULT_REGRESSOR_SIGMA, COEFFICIENT_DF_COLS, PredictMethod, PredictionKeys, TrainingMetaKeys, PredictionMetaKeys
  from ..exceptions import IllegalArgument, ModelException, PredictionException
  from .ets import ETSModel
  from ..estimators.stan_estimator import StanEstimatorMCMC, StanEstimatorMAP
label_desc: Use image node_id 14 for calling the DLTModel obj's underlying member method code with example usage: obj.predict(posterior_estimates, df, training_meta, prediction_meta, include_error) and returns: out