code: def etsmodel(
      y: np.ndarray,
      m: int,
      errortype: str,
      trendtype: str,
      seasontype: str,
      damped: bool,
      alpha: float,
      beta: float,
      gamma: float,
      phi: float,
      lower: np.ndarray,
      upper: np.ndarray,
      opt_crit: str,
      nmse: int,
      bounds: str,
      maxit: int = 2_000,
      control=None,
      seed=None,
      trace: bool = False,
  ):
      if seasontype == "N":
          m = 1
      # if not np.isnan(alpha):
      #    upper[2] = min(alpha, upper[2])
      #    upper[3] = min(1 - alpha, upper[3])
      # if not np.isnan(beta):
      #    lower[1] = max(beta, lower[1])
      #    upper[1] = min(1 - gamma, upper[1])
      par_ = initparam(
          alpha,
          beta,
          gamma,
          phi,
          trendtype,
          seasontype,
          damped,
          lower,
          upper,
          m,
          bounds,
      )
      par_noopt = dict(alpha=alpha, beta=beta, gamma=gamma, phi=phi)
  
      if not np.isnan(par_["alpha"]):
          alpha = par_["alpha"]
      if not np.isnan(par_["beta"]):
          beta = par_["beta"]
      if not np.isnan(par_["gamma"]):
          gamma = par_["gamma"]
      if not np.isnan(par_["phi"]):
          phi = par_["phi"]
      if not check_param(
          alpha, beta, gamma, phi, lower, upper, bounds, m
      ):
          raise Exception("Parameters out of range")
      # initialize state
      init_state = initstate(y, m, trendtype, seasontype)
      nstate = len(init_state)
      par_ = {
          key: val for key, val in par_.items() if not np.isnan(val)
      }
      par = np.full(len(par_) + nstate, fill_value=np.nan)
      par[: len(par_)] = list(par_.values())
      par[len(par_) :] = init_state
      lower_ = np.full_like(par, fill_value=-np.inf)
      upper_ = np.full_like(par, fill_value=np.inf)
      j = 0
      for i, pr in enumerate(["alpha", "beta", "gamma", "phi"]):
          if pr in par_.keys():
              lower_[j] = lower[i]
              upper_[j] = upper[i]
              j += 1
      lower = lower_
      upper = upper_
      np_ = len(par)
      if np_ >= len(y) - 1:
          return dict(
              aic=np.inf,
              bic=np.inf,
              aicc=np.inf,
              mse=np.inf,
              amse=np.inf,
              fit=None,
              par=par,
              states=init_state,
          )
  
      fred = optimize_ets_target_fn(
          x0=par,
          par=par_,
          y=y,
          nstate=nstate,
          errortype=errortype,
          trendtype=trendtype,
          seasontype=seasontype,
          damped=damped,
          par_noopt=par_noopt,
          lowerb=lower,
          upperb=upper,
          opt_crit=opt_crit,
          nmse=nmse,
          bounds=bounds,
          m=m,
          pnames=par_.keys(),
          pnames2=par_noopt.keys(),
      )
      fit_par = fred.x
      init_state = fit_par[-nstate:]
      if seasontype != "N":
          init_state = np.hstack(
              [
                  init_state,
                  m * (seasontype == "M")
                  - init_state[(1 + (trendtype != "N")) : nstate].sum(),
              ]
          )
      j = 0
      if not np.isnan(fit_par[j]):
          alpha = fit_par[j]
          j += 1
      if trendtype != "N":
          if not np.isnan(fit_par[j]):
              beta = fit_par[j]
          j += 1
      if seasontype != "N":
          if not np.isnan(fit_par[j]):
              gamma = fit_par[j]
          j += 1
      if damped:
          if not np.isnan(fit_par[j]):
              phi = fit_par[j]
  
      amse, e, states, lik = pegelsresid_C(
          y,
          m,
          init_state,
          errortype,
          trendtype,
          seasontype,
          damped,
          alpha,
          beta,
          gamma,
          phi,
          nmse,
      )
      np_ = np_ + 1
      ny = len(y)
      aic = lik + 2 * np_
      bic = lik + np.log(ny) * np_
      if ny - np_ - 1 != 0.0:
          aicc = aic + 2 * np_ * (np_ + 1) / (ny - np_ - 1)
      else:
          aicc = np.inf
  
      mse = amse[0]
      amse = np.mean(amse)
  
      fit_par = np.concatenate([[alpha, beta, gamma, phi], init_state])
      if errortype == "A":
          fits = y - e
      else:
          fits = y / (1 + e)
  
      sigma2 = np.sum(e**2) / (ny - np_ - 1)
  
      return dict(
          loglik=-0.5 * lik,
          aic=aic,
          bic=bic,
          aicc=aicc,
          mse=mse,
          amse=amse,
          fit=fred,
          residuals=e,
          components=f"{errortype}{trendtype}{seasontype}{'D' if damped else 'N'}",
          m=m,
          nstate=nstate,
          fitted=fits,
          states=states,
          par=fit_par,
          sigma2=sigma2,
          n_params=np_,
      )
  
imports: import math
  from collections import namedtuple
  from typing import Tuple
  import numpy
  from numba import njit
  from numba.typed import List
  from statsmodels.tsa.seasonal import seasonal_decompose
  from .utils import _calculate_intervals, CACHE, NOGIL
label_desc: Use image node_id 19 for calling a global function with example usage: etsmodel(y, m, errortype, trendtype, seasontype, damped, alpha, beta, gamma, phi, lower, upper, opt_crit, nmse, bounds, maxit, control, seed, trace) and returns: dict, dict