Repository: statsmodels/statsmodels

Issue:
BUG: full_output in optimizers Likelihood model
full_output=False

In this case, the default fit function (newton) returns one array (newparams).  However, LikelihoodModel.fit() expects 2.  fit() then tries to unpack newparams into two variables, and thus gives the ValueError "too many values to unpack"  (unless for some reason newparams is of length 2). (Ian Langmore on mailinglist)

if params is of length 2, using newton silently cuts off one parameter.

looking at the code, I also found that xopt is not defined in any of the full_output=False cases:

```
    return xopt, retvals
UnboundLocalError: local variable 'xopt' referenced before assignment
```



Discussion:
needs regression tests to avoid refactoring bugs.

Is there a small example that demonstrates this that can be used for testing?

It looks to me like a lot of these are going to be broken if full_output = False. I wonder if it's a refactoring victim or if it ever worked... Need to think a bit about what we want and look at the new optimize interface in scipy for guidance.


Resolving patch:
diff --git a/statsmodels/base/model.py b/statsmodels/base/model.py
--- a/statsmodels/base/model.py
+++ b/statsmodels/base/model.py
@@ -323,14 +323,9 @@ def fit(self, start_params=None, method='newton', maxiter=100,
                              retall=retall, full_output=full_output,
                              hess=hess)
 
-        if not full_output:
+        if not full_output: # xopt should be None and retvals is argmin
             xopt = retvals
 
-        # NOTE: better just to use the Analytic Hessian here, as approximation
-        # isn't great
-#        if method == 'bfgs' and full_output:
-#            Hinv = retvals.setdefault('Hinv', 0)
-        # If we have full_output, then compute the inverse Hessian
         elif cov_params_func:
             Hinv = cov_params_func(self, xopt, retvals)
         elif method == 'newton' and full_output:
@@ -406,12 +401,15 @@ def _fit_mle_newton(f, score, start_params, fargs, kwargs, disp=True,
                    'converged': converged}
         if retall:
             retvals.update({'allvecs': history})
+
     else:
-        return newparams
+        retvals = newparams
+        xopt = None
 
     return xopt, retvals
 
 
+
 def _fit_mle_bfgs(f, score, start_params, fargs, kwargs, disp=True,
                     maxiter=100, callback=None, retall=False,
                     full_output=True, hess=None):
@@ -434,6 +432,8 @@ def _fit_mle_bfgs(f, score, start_params, fargs, kwargs, disp=True,
                 warnflag, 'converged': converged}
         if retall:
             retvals.update({'allvecs': allvecs})
+    else:
+        xopt = None
 
     return xopt, retvals
 
@@ -459,6 +459,8 @@ def _fit_mle_nm(f, score, start_params, fargs, kwargs, disp=True,
                    'converged': converged}
         if retall:
             retvals.update({'allvecs': allvecs})
+    else:
+        xopt = None
 
     return xopt, retvals
 
@@ -484,6 +486,9 @@ def _fit_mle_cg(f, score, start_params, fargs, kwargs, disp=True,
         if retall:
             retvals.update({'allvecs': allvecs})
 
+    else:
+        xopt = None
+
     return xopt, retvals
 
 
@@ -510,6 +515,8 @@ def _fit_mle_ncg(f, score, start_params, fargs, kwargs, disp=True,
                    'converged': converged}
         if retall:
             retvals.update({'allvecs': allvecs})
+    else:
+        xopt = None
 
     return xopt, retvals
 
@@ -538,6 +545,8 @@ def _fit_mle_powell(f, score, start_params, fargs, kwargs, disp=True,
                    'converged': converged}
         if retall:
             retvals.update({'allvecs': allvecs})
+    else:
+        xopt = None
 
     return xopt, retvals
 


Test patch:
diff --git a/statsmodels/base/tests/test_optimize.py b/statsmodels/base/tests/test_optimize.py
new file mode 100644
--- /dev/null
+++ b/statsmodels/base/tests/test_optimize.py
@@ -0,0 +1,49 @@
+from numpy.testing import assert_
+from statsmodels.base.model import (_fit_mle_newton, _fit_mle_nm,
+                                    _fit_mle_bfgs, _fit_mle_cg,
+                                    _fit_mle_ncg, _fit_mle_powell)
+
+fit_funcs = {
+    'newton': _fit_mle_newton,
+    'nm': _fit_mle_nm,  # Nelder-Mead
+    'bfgs': _fit_mle_bfgs,
+    'cg': _fit_mle_cg,
+    'ncg': _fit_mle_ncg,
+    'powell': _fit_mle_powell
+            }
+
+
+def dummy_func(x):
+    return x**2
+
+def dummy_score(x):
+    return 2*x
+
+def dummy_hess(x):
+    return [[2]]
+
+def test_full_output_false():
+    # just a smoke test
+
+    # newton needs f, score, start, fargs, kwargs
+    # bfgs needs f, score start, fargs, kwargs
+    # nm needs ""
+    # cg ""
+    # ncg ""
+    # powell ""
+    for method in fit_funcs:
+        func = fit_funcs[method]
+        if method == "newton":
+            xopts, retvals = func(dummy_func, dummy_score, [1], (), {},
+                    hess=dummy_hess, full_output=False, disp=0)
+
+        else:
+            xopts, retvals = func(dummy_func, dummy_score, [1], (), {},
+                full_output=False, disp=0)
+        assert_(xopts == None)
+        if method == "powell":
+            #NOTE: I think I reported this? Might be version/optimize API
+            # dependent
+            assert_(retvals.shape == () and retvals.size == 1)
+        else:
+            assert_(len(retvals)==1)