Repository: statsmodels/statsmodels

Issue:
BUG: ETS_Model fails to converge for basic model.
#### Describe the bug

ETS_Model fails to converge -- or even move off starting value, in a simple model.

#### Code Sample, a copy-pastable example if possible


```python
from statsmodels.tsa.exponential_smoothing.ets import ETSModel
from statsmodels.tsa.statespace.exponential_smoothing import ExponentialSmoothing

import numpy as np
gen = np.random.default_rng(0)
e = gen.standard_normal(12000)
y = e.copy()
for i in range(1, e.shape[0]):
    y[i] = y[i-1] - 0.2 * e[i-1] + e[i]
y = y[200:]
res= ExponentialSmoothing(y).fit()
ets_res= ETSModel(y).fit()
```
<details>

**Note**: As you can see, there are many issues on our GitHub tracker, so it is very possible that your issue has been posted before. Please check first before submitting so that we do not have to handle and close duplicates.

**Note**: Please be sure you are using the latest released version of `statsmodels`, or a recent build of `master`. If your problem has been fixed in an unreleased version, you might be able to use `master` until a new release occurs. 

**Note**: If you are using a released version, have you verified that the bug exists in the master branch of this repository? It helps the limited resources if we know problems exist in the current master so that they do not need to check whether the code sample produces a bug in the next release.

</details>


#### Expected Output

Should converge like EstponentialSmoothing does. Instead see
```
C:\Anaconda\lib\site-packages\scipy\optimize\_numdiff.py:519: RuntimeWarning: invalid value encountered in true_divide
  J_transposed[i] = df / dx
c:\git\statsmodels\statsmodels\base\model.py:568: ConvergenceWarning: Maximum Likelihood optimization failed to converge. Check mle_retvals
  "Check mle_retvals", ConvergenceWarning)
```

and `summary()`

```
"""
                                 ETS Results                                  
==============================================================================
Dep. Variable:                      y   No. Observations:                11800
Model:                       ETS(ANN)   Log Likelihood              -24532.560
Date:                Mon, 13 Jul 2020   AIC                          49069.121
Time:                        17:56:06   BIC                          49083.872
Sample:                             0   HQIC                         49074.073
                              - 11800                                         
Covariance Type:               approx                                         
===================================================================================
                      coef    std err          z      P>|z|      [0.025      0.975]
-----------------------------------------------------------------------------------
smoothing_level     0.1000      0.001     79.183      0.000       0.098       0.102
initial_level       1.3786      0.843      1.635      0.102      -0.275       3.032
===================================================================================
Ljung-Box (Q):                         nan   Jarque-Bera (JB):                  nan
Prob(Q):                               nan   Prob(JB):                          nan
Heteroskedasticity (H):                nan   Skew:                              nan
Prob(H) (two-sided):                   nan   Kurtosis:                          nan
===================================================================================

Warnings:
[1] Covariance matrix calculated using numerical (complex-step) differentiation
```
#### Output of ``import statsmodels.api as sm; sm.show_versions()``

<details>

Tested against master.

</details>



Discussion:
@s-scherrer any idea why it fails here?
I just tried running this on my machine, here it converges and the value of alpha is similar to the one obtained with `ExponentialSmoothing` (0.8049 vs 0.8050).

<details>

```
In [1]: from statsmodels.tsa.exponential_smoothing.ets import ETSModel 
   ...: from statsmodels.tsa.statespace.exponential_smoothing import Exponential
   ...: Smoothing 
   ...:  
   ...: import numpy as np 
   ...: gen = np.random.default_rng(0) 
   ...: e = gen.standard_normal(12000) 
   ...: y = e.copy() 
   ...: for i in range(1, e.shape[0]): 
   ...:     y[i] = y[i-1] - 0.2 * e[i-1] + e[i] 
   ...: y = y[200:] 
   ...: res= ExponentialSmoothing(y).fit() 
   ...: ets_res= ETSModel(y).fit()                                              
RUNNING THE L-BFGS-B CODE

           * * *

Machine precision = 2.220D-16
 N =            2     M =           10
 This problem is unconstrained.

At X0         0 variables are exactly at the bounds

At iterate    0    f=  2.07904D+00    |proj g|=  4.18003D-01

At iterate    5    f=  1.41871D+00    |proj g|=  1.52936D-04

At iterate   10    f=  1.41871D+00    |proj g|=  8.24925D-05

           * * *

Tit   = total number of iterations
Tnf   = total number of function evaluations
Tnint = total number of segments explored during Cauchy searches
Skip  = number of BFGS updates skipped
Nact  = number of active bounds at final generalized Cauchy point
Projg = norm of the final projected gradient
F     = final function value

           * * *

   N    Tit     Tnf  Tnint  Skip  Nact     Projg        F
    2     14     15      1     0     0   1.547D-06   1.419D+00
  F =   1.4187097536168372     

CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL            
RUNNING THE L-BFGS-B CODE

           * * *

Machine precision = 2.220D-16
 N =            7     M =           10

At X0         5 variables are exactly at the bounds

At iterate    0    f=  2.07903D+00    |proj g|=  8.99900D-01

At iterate    1    f=  1.43785D+00    |proj g|=  1.92631D-01

At iterate    2    f=  1.43156D+00    |proj g|=  1.58551D-01

At iterate    3    f=  1.41874D+00    |proj g|=  7.93901D-03

At iterate    4    f=  1.41871D+00    |proj g|=  5.61018D-04

At iterate    5    f=  1.41871D+00    |proj g|=  2.27596D-05

At iterate    6    f=  1.41871D+00    |proj g|=  2.27374D-05

           * * *

Tit   = total number of iterations
Tnf   = total number of function evaluations
Tnint = total number of segments explored during Cauchy searches
Skip  = number of BFGS updates skipped
Nact  = number of active bounds at final generalized Cauchy point
Projg = norm of the final projected gradient
F     = final function value

           * * *

   N    Tit     Tnf  Tnint  Skip  Nact     Projg        F
    7      6      8      7     0     5   2.274D-05   1.419D+00
  F =   1.4187126931425427     

CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH             

In [2]: ets_res.summary()                                                                                                                                                                        
Out[2]: 
<class 'statsmodels.iolib.summary.Summary'>
"""
                                 ETS Results                                  
==============================================================================
Dep. Variable:                      y   No. Observations:                11800
Model:                       ETS(ANN)   Log Likelihood              -16740.810
Date:                Mon, 13 Jul 2020   AIC                          33485.620
Time:                        20:09:03   BIC                          33500.371
Sample:                             0   HQIC                         33490.572
                              - 11800                                         
Covariance Type:               approx                                         
===================================================================================
                      coef    std err          z      P>|z|      [0.025      0.975]
-----------------------------------------------------------------------------------
smoothing_level     0.8049      0.009     90.263      0.000       0.787       0.822
initial_level       1.3787      0.981      1.406      0.160      -0.543       3.301
===================================================================================
Ljung-Box (Q):                         nan   Jarque-Bera (JB):                  nan
Prob(Q):                               nan   Prob(JB):                          nan
Heteroskedasticity (H):                nan   Skew:                              nan
Prob(H) (two-sided):                   nan   Kurtosis:                          nan
===================================================================================

Warnings:
[1] Covariance matrix calculated using numerical (complex-step) differentiation.
"""

In [3]: res.summary()                                                                                                                                                                            
Out[3]: 
<class 'statsmodels.iolib.summary.Summary'>
"""
                        Exponential Smoothing Results                         
==============================================================================
Dep. Variable:                      y   No. Observations:                11800
Model:                   ETS(A, N, N)   Log Likelihood              -16740.775
Date:                Mon, 13 Jul 2020   AIC                          33487.550
Time:                        20:10:29   BIC                          33509.678
Sample:                             0   HQIC                         33494.979
                              - 11800   Scale                            1.000
Covariance Type:                  opg                                         
===================================================================================
                      coef    std err          z      P>|z|      [0.025      0.975]
-----------------------------------------------------------------------------------
smoothing_level     0.8050      0.009     88.069      0.000       0.787       0.823
initial_level       1.6348      5.583      0.293      0.770      -9.307      12.577
===================================================================================
Ljung-Box (Q):                       31.78   Jarque-Bera (JB):                 1.52
Prob(Q):                              0.82   Prob(JB):                         0.47
Heteroskedasticity (H):               0.99   Skew:                            -0.03
Prob(H) (two-sided):                  0.73   Kurtosis:                         3.01
===================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
"""
```

</details>

Output of sm.show_versions():

<details>

```
INSTALLED VERSIONS
------------------
Python: 3.8.3.final.0
OS: Linux 5.7.7-1-default #1 SMP Wed Jul 1 19:03:27 UTC 2020 (cba119b) x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8

statsmodels
===========

Installed: v0.12.0.dev0+567.g4f226bc35 (/home/samuel/Documents/OpenSource/statsmodels/statsmodels)

Required Dependencies
=====================

cython: 0.29.20 (/home/samuel/.pyenv/versions/statsmodels/lib64/python3.8/site-packages/Cython)
numpy: 1.18.5 (/home/samuel/.pyenv/versions/statsmodels/lib64/python3.8/site-packages/numpy)
scipy: 1.4.1 (/home/samuel/.pyenv/versions/statsmodels/lib64/python3.8/site-packages/scipy)
pandas: 1.0.4 (/home/samuel/.pyenv/versions/statsmodels/lib64/python3.8/site-packages/pandas)
    dateutil: 2.8.1 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/dateutil)
patsy: 0.5.1 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/patsy)

Optional Dependencies
=====================

matplotlib: 3.2.1 (/home/samuel/.pyenv/versions/statsmodels/lib64/python3.8/site-packages/matplotlib)
    backend: TkAgg 
cvxopt: Not installed
joblib: Not installed

Developer Tools
================

IPython: 7.15.0 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/IPython)
    jinja2: 2.11.2 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/jinja2)
sphinx: 3.1.0 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/sphinx)
    pygments: 2.6.1 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/pygments)
pytest: 5.4.3 (/home/samuel/.pyenv/versions/statsmodels/lib/python3.8/site-packages/pytest)
virtualenv: Not installed
```
</details>

Resolving patch:
diff --git a/statsmodels/tsa/exponential_smoothing/ets.py b/statsmodels/tsa/exponential_smoothing/ets.py
--- a/statsmodels/tsa/exponential_smoothing/ets.py
+++ b/statsmodels/tsa/exponential_smoothing/ets.py
@@ -966,7 +966,7 @@ def fit(
             internal_start_params = self._convert_and_bound_start_params(
                 start_params
             )
-            kwargs["bounds"] = self._setup_bounds()
+            bounds = self._setup_bounds()
 
             # check if we need to use the starred parameters
             use_beta_star = "smoothing_trend" not in self.bounds
@@ -976,6 +976,21 @@ def fit(
             if use_gamma_star:
                 internal_start_params[2] /= 1 - internal_start_params[0]
 
+            # check if we have fixed parameters and remove them from the
+            # parameter vector
+            is_fixed = np.zeros(self._k_params_internal, dtype=int)
+            fixed_values = np.empty_like(internal_start_params)
+            params_without_fixed = []
+            kwargs["bounds"] = []
+            for i in range(self._k_params_internal):
+                if bounds[i][0] == bounds[i][1]:
+                    is_fixed[i] = True
+                    fixed_values[i] = bounds[i][0]
+                else:
+                    params_without_fixed.append(internal_start_params[i])
+                    kwargs["bounds"].append(bounds[i])
+            params_without_fixed = np.asarray(params_without_fixed)
+
             # pre-allocate memory for smoothing results
             yhat = np.zeros(self.nobs)
             xhat = np.zeros((self.nobs, self._k_states_internal))
@@ -983,8 +998,14 @@ def fit(
             kwargs["approx_grad"] = True
             with self.use_internal_loglike():
                 mlefit = super().fit(
-                    internal_start_params,
-                    fargs=(yhat, xhat, use_beta_star, use_gamma_star),
+                    params_without_fixed,
+                    fargs=(
+                        yhat,
+                        xhat,
+                        is_fixed,
+                        fixed_values,
+                        use_beta_star,
+                        use_gamma_star),
                     method="lbfgs",
                     maxiter=maxiter,
                     full_output=full_output,
@@ -994,11 +1015,21 @@ def fit(
                     **kwargs,
                 )
             # convert params back
+            # first, insert fixed params
+            fitted_params = np.empty_like(internal_start_params)
+            idx_without_fixed = 0
+            for i in range(self._k_params_internal):
+                if is_fixed[i]:
+                    fitted_params[i] = fixed_values[i]
+                else:
+                    fitted_params[i] = mlefit.params[idx_without_fixed]
+                    idx_without_fixed += 1
+
             if use_beta_star:
-                mlefit.params[1] *= mlefit.params[0]
+                fitted_params[1] *= fitted_params[0]
             if use_gamma_star:
-                mlefit.params[2] *= 1 - mlefit.params[0]
-            final_params = self._model_params(mlefit.params)
+                fitted_params[2] *= 1 - fitted_params[0]
+            final_params = self._model_params(fitted_params)
 
         if return_params:
             return final_params
@@ -1010,7 +1041,8 @@ def fit(
             return result
 
     def _loglike_internal(
-        self, params, yhat, xhat, use_beta_star=False, use_gamma_star=False
+            self, params, yhat, xhat, is_fixed=None, fixed_values=None,
+            use_beta_star=False, use_gamma_star=False
     ):
         """
         Log-likelihood function to be called from fit to avoid reallocation of
@@ -1020,13 +1052,20 @@ def _loglike_internal(
         ----------
         params : np.ndarray of np.float
             Model parameters: (alpha, beta, gamma, phi, l[-1],
-            b[-1], s[-1], ..., s[-m]). This must be in the format of internal
-            parameters.
+            b[-1], s[-1], ..., s[-m]). If there are no fixed values this must
+            be in the format of internal parameters. Otherwise the fixed values
+            are skipped.
         yhat : np.ndarray
             Array of size (n,) where fitted values will be written to.
         xhat : np.ndarray
             Array of size (n, _k_states_internal) where fitted states will be
             written to.
+        is_fixed : np.ndarray or None
+            Boolean array indicating values which are fixed during fitting.
+            This must have the full length of internal parameters.
+        fixed_values : np.ndarray or None
+            Array of fixed values (arbitrary values for non-fixed parameters)
+            This must have the full length of internal parameters.
         use_beta_star : boolean
             Whether to internally use beta_star as parameter
         use_gamma_star : boolean
@@ -1036,8 +1075,16 @@ def _loglike_internal(
             data = np.asarray(self.endog, dtype=complex)
         else:
             data = self.endog
+
+        if is_fixed is None:
+            is_fixed = np.zeros(self._k_params_internal, dtype=int)
+            fixed_values = np.empty(
+                self._k_params_internal, dtype=params.dtype
+            )
+
         self._smoothing_func(
-            params, data, yhat, xhat, use_beta_star, use_gamma_star
+            params, data, yhat, xhat, is_fixed, fixed_values, use_beta_star,
+            use_gamma_star
         )
         res = self._residuals(yhat, data=data)
         logL = -self.nobs / 2 * (np.log(2 * np.pi * np.mean(res ** 2)) + 1)
@@ -1128,7 +1175,11 @@ def _smooth(self, params):
         internal_params = self._internal_params(params)
         yhat = np.zeros(self.nobs)
         xhat = np.zeros((self.nobs, self._k_states_internal))
-        self._smoothing_func(internal_params, self.endog, yhat, xhat)
+        is_fixed = np.zeros(self._k_params_internal, dtype=int)
+        fixed_values = np.empty(self._k_params_internal, dtype=params.dtype)
+        self._smoothing_func(
+            internal_params, self.endog, yhat, xhat, is_fixed, fixed_values
+        )
 
         # remove states that are only internal
         states = self._get_states(xhat)
@@ -1548,6 +1599,9 @@ def simulate(
         # set initial values and obtain parameters
         start_params = self._get_prediction_params(start_idx)
         x = np.zeros((nsimulations, self.model._k_states_internal))
+        # is fixed and fixed values are dummy arguments
+        is_fixed = np.zeros(len(start_params), dtype=int)
+        fixed_values = np.zeros_like(start_params)
         (
             alpha,
             beta_star,
@@ -1555,7 +1609,9 @@ def simulate(
             phi,
             m,
             _,
-        ) = smooth._initialize_ets_smooth(start_params, x)
+        ) = smooth._initialize_ets_smooth(
+            start_params, x, is_fixed, fixed_values
+        )
         beta = alpha * beta_star
         gamma = (1 - alpha) * gamma_star
         # make x a 3 dimensional matrix: first dimension is nsimulations


Test patch:
diff --git a/statsmodels/tsa/tests/test_exponential_smoothing.py b/statsmodels/tsa/tests/test_exponential_smoothing.py
--- a/statsmodels/tsa/tests/test_exponential_smoothing.py
+++ b/statsmodels/tsa/tests/test_exponential_smoothing.py
@@ -12,6 +12,7 @@
 import scipy.stats
 
 from statsmodels.tsa.exponential_smoothing.ets import ETSModel
+from statsmodels.tsa.holtwinters import ExponentialSmoothing
 
 # This contains tests for the exponential smoothing implementation in
 # tsa/exponential_smoothing/ets.py.
@@ -440,17 +441,7 @@ def test_fit_vs_R(setup_model, reset_randomstate):
     const = -model.nobs / 2 * (np.log(2 * np.pi / model.nobs) + 1)
     loglike_R = results_R["loglik"][0] + const
     loglike = fit.llf
-    try:
-        assert loglike >= loglike_R - 1e-4
-    except AssertionError:
-        # This is a fail safe to ensure that the model works
-        fit = model.fit(disp=True, start_params=params)
-        loglike = fit.llf
-        assert loglike >= loglike_R - 1e-4
-        pytest.xfail(
-            "Starting values did not produce an adequate fit. They should"
-            "be improved."
-        )
+    assert loglike >= loglike_R - 1e-4
 
 
 def test_predict_vs_R(setup_model):
@@ -532,11 +523,13 @@ def test_bounded_fit(oildata):
         fit2 = model2.fit(disp=False)
     assert fit2.smoothing_trend == 0.99
     assert_allclose(fit1.params, fit2.params)
+    fit2.summary()  # check if summary runs without failing
 
     # using fit_constrained
     fit3 = model2.fit_constrained({"smoothing_trend": 0.99})
     assert fit3.smoothing_trend == 0.99
     assert_allclose(fit1.params, fit3.params)
+    fit3.summary()
 
 
 def test_seasonal_periods(austourists):
@@ -618,3 +611,31 @@ def test_hessian(austourists_model):
     austourists_model.hessian(
         fit.params, approx_complex_step=False, approx_centered=True,
     )
+
+
+def test_convergence_simple():
+    # issue 6883
+    gen = np.random.RandomState(0)
+    e = gen.standard_normal(12000)
+    y = e.copy()
+    for i in range(1, e.shape[0]):
+        y[i] = y[i - 1] - 0.2 * e[i - 1] + e[i]
+    y = y[200:]
+    res = ExponentialSmoothing(y).fit()
+    ets_res = ETSModel(y).fit()
+
+    # the smoothing level should be very similar, the initial state might be
+    # different as it doesn't influence the final result too much
+    assert_almost_equal(
+        res.params['smoothing_level'],
+        ets_res.smoothing_level,
+        3
+    )
+
+    # the first few values are influenced by differences in initial state, so
+    # we don't test them here
+    assert_almost_equal(
+        res.fittedvalues[10:],
+        ets_res.fittedvalues[10:],
+        3
+    )