From 3b2b273e5fa647dd4be2b7661182733c5ba8425b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 19 May 2023 21:26:40 -0700 Subject: [PATCH] MAINT: minimize_ipopt: fix late binding bug --- cyipopt/scipy_interface.py | 7 +++++-- cyipopt/tests/unit/test_scipy_optional.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cyipopt/scipy_interface.py b/cyipopt/scipy_interface.py index b74ac010..4843c463 100644 --- a/cyipopt/scipy_interface.py +++ b/cyipopt/scipy_interface.py @@ -157,8 +157,11 @@ def __init__(self, con_hessian = con.get('hess', None) con_kwargs = con.get('kwargs', {}) if con_jac is None: - con_jac = lambda x0, *args, **kwargs: approx_fprime( - x0, con_fun, eps, *args, **kwargs) + # beware of late binding! + def con_jac(x, *args, con_fun=con_fun, **kwargs): + def wrapped(x): + return con_fun(x, *args, **kwargs) + return approx_fprime(x, wrapped, eps) elif con_jac is True: con_fun = MemoizeJac(con_fun) con_jac = con_fun.derivative diff --git a/cyipopt/tests/unit/test_scipy_optional.py b/cyipopt/tests/unit/test_scipy_optional.py index e636a613..be690028 100644 --- a/cyipopt/tests/unit/test_scipy_optional.py +++ b/cyipopt/tests/unit/test_scipy_optional.py @@ -365,3 +365,23 @@ def con_ineq_hess(x, v): assert res.get("success") is True expected_res = np.array([0.99999999, 4.74299964, 3.82114998, 1.3794083]) np.testing.assert_allclose(res.get("x"), expected_res) + + +@pytest.mark.skipif("scipy" not in sys.modules, + reason="Test only valid if Scipy available.") +def test_minimize_late_binding_bug(): + # `IpoptProblemWrapper` had a late binding bug when constraint Jacobians + # were defined with `optimize.approx_fprime`. Check that this is resolved. + from scipy.optimize import minimize + + fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + cons = ({'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2}, + {'type': 'ineq', 'fun': lambda x: -x[0] - 2 * x[1] + 6}, + {'type': 'ineq', 'fun': lambda x: -x[0] + 2 * x[1] + 2}) + bnds = ((0, None), (0, None)) + + res = cyipopt.minimize_ipopt(fun, (2, 0), bounds=bnds, constraints=cons) + ref = minimize(fun, (2, 0), bounds=bnds, constraints=cons) + assert res.success + np.testing.assert_allclose(res.x, ref.x) + np.testing.assert_allclose(res.fun, ref.fun)