Repository: statsmodels/statsmodels

Issue:
MAINT: Scipy minimize wrapper Nelder-Mead and Powell bounds
The `_fit_minimize` wrapper function for `scipy.optimize.minimize` does not reflect more recent scipy versions which have bounds support for Nelder-Mead and Powell methods.

https://docs.scipy.org/doc/scipy-1.8.1/reference/generated/scipy.optimize.minimize.html
https://docs.scipy.org/doc/scipy-1.8.1/reference/optimize.minimize-neldermead.html
https://docs.scipy.org/doc/scipy-1.8.1/reference/optimize.minimize-powell.html

Update the `has_bounds` list in `_fit_minimize`:
Add `'Powell'` to reflect scipy 1.5.0 scipy/scipy#12172
Add `'Nelder-Mead'` to reflect scipy 1.7.0 scipy/scipy#13515


Discussion:

Resolving patch:
diff --git a/statsmodels/base/optimizer.py b/statsmodels/base/optimizer.py
--- a/statsmodels/base/optimizer.py
+++ b/statsmodels/base/optimizer.py
@@ -7,6 +7,7 @@
 from typing import Any, Sequence
 import numpy as np
 from scipy import optimize
+from statsmodels.compat.scipy import SP_LT_15, SP_LT_17
 
 
 def check_kwargs(kwargs: dict[str, Any], allowed: Sequence[str], method: str):
@@ -343,6 +344,12 @@ def _fit_minimize(f, score, start_params, fargs, kwargs, disp=True,
 
     # Use bounds/constraints only if they're allowed by the method
     has_bounds = ['L-BFGS-B', 'TNC', 'SLSQP', 'trust-constr']
+    # Added in SP 1.5
+    if not SP_LT_15:
+        has_bounds += ['Powell']
+    # Added in SP 1.7
+    if not SP_LT_17:
+        has_bounds += ['Nelder-Mead']
     has_constraints = ['COBYLA', 'SLSQP', 'trust-constr']
 
     if 'bounds' in kwargs.keys() and kwargs['min_method'] in has_bounds:
diff --git a/statsmodels/compat/scipy.py b/statsmodels/compat/scipy.py
--- a/statsmodels/compat/scipy.py
+++ b/statsmodels/compat/scipy.py
@@ -7,6 +7,7 @@
 SP_LT_15 = SP_VERSION < Version("1.4.99")
 SCIPY_GT_14 = not SP_LT_15
 SP_LT_16 = SP_VERSION < Version("1.5.99")
+SP_LT_17 = SP_VERSION < Version("1.6.99")
 SP_LT_19 = SP_VERSION < Version("1.8.99")
 
 


Test patch:
diff --git a/statsmodels/base/tests/test_optimize.py b/statsmodels/base/tests/test_optimize.py
--- a/statsmodels/base/tests/test_optimize.py
+++ b/statsmodels/base/tests/test_optimize.py
@@ -1,4 +1,4 @@
-from statsmodels.compat.scipy import SP_LT_15
+from statsmodels.compat.scipy import SP_LT_15, SP_LT_17
 import pytest
 from numpy.testing import assert_
 from numpy.testing import assert_almost_equal
@@ -48,6 +48,10 @@ def dummy_bounds():
     return ((0, None), (0, None))
 
 
+def dummy_bounds_tight():
+    return ((2, None), (3.5, None))
+
+
 def dummy_constraints():
     cons = (
         {"type": "ineq", "fun": lambda x: x[0] - 2 * x[1] + 2},
@@ -138,3 +142,41 @@ def test_minimize_scipy_slsqp():
         disp=0,
     )
     assert_almost_equal(xopt, [1.4, 1.7], 4)
+
+
+@pytest.mark.skipif(SP_LT_15, reason="Powell bounds support added in SP 1.5")
+def test_minimize_scipy_powell():
+    func = fit_funcs["minimize"]
+    xopt, _ = func(
+        dummy_bounds_constraint_func,
+        None,
+        (3, 4.5),
+        (),
+        {
+            "min_method": "Powell",
+            "bounds": dummy_bounds_tight(),
+        },
+        hess=None,
+        full_output=False,
+        disp=0,
+    )
+    assert_almost_equal(xopt, [2, 3.5], 4)
+
+
+@pytest.mark.skipif(SP_LT_17, reason="NM bounds support added in SP 1.7")
+def test_minimize_scipy_nm():
+    func = fit_funcs["minimize"]
+    xopt, _ = func(
+        dummy_bounds_constraint_func,
+        None,
+        (3, 4.5),
+        (),
+        {
+            "min_method": "Nelder-Mead",
+            "bounds": dummy_bounds_tight(),
+        },
+        hess=None,
+        full_output=False,
+        disp=0,
+    )
+    assert_almost_equal(xopt, [2, 3.5], 4)