""" Unit test for constraint conversion """ import numpy as np from numpy.testing import (assert_array_almost_equal, assert_allclose, assert_warns, suppress_warnings) import pytest from scipy.optimize import (NonlinearConstraint, LinearConstraint, OptimizeWarning, minimize, BFGS) from .test_minimize_constrained import (Maratos, HyperbolicIneq, Rosenbrock, IneqRosenbrock, EqIneqRosenbrock, BoundedRosenbrock, Elec) class TestOldToNew(object): x0 = (2, 0) bnds = ((0, None), (0, None)) method = "trust-constr" def test_constraint_dictionary_1(self): 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}) with suppress_warnings() as sup: sup.filter(UserWarning, "delta_grad == 0.0") res = minimize(fun, self.x0, method=self.method, bounds=self.bnds, constraints=cons) assert_allclose(res.x, [1.4, 1.7], rtol=1e-4) assert_allclose(res.fun, 0.8, rtol=1e-4) def test_constraint_dictionary_2(self): fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 cons = {'type': 'eq', 'fun': lambda x, p1, p2: p1*x[0] - p2*x[1], 'args': (1, 1.1), 'jac': lambda x, p1, p2: np.array([[p1, -p2]])} with suppress_warnings() as sup: sup.filter(UserWarning, "delta_grad == 0.0") res = minimize(fun, self.x0, method=self.method, bounds=self.bnds, constraints=cons) assert_allclose(res.x, [1.7918552, 1.62895927]) assert_allclose(res.fun, 1.3857466063348418) def test_constraint_dictionary_3(self): fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 cons = [{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2}, NonlinearConstraint(lambda x: x[0] - x[1], 0, 0)] with suppress_warnings() as sup: sup.filter(UserWarning, "delta_grad == 0.0") res = minimize(fun, self.x0, method=self.method, bounds=self.bnds, constraints=cons) assert_allclose(res.x, [1.75, 1.75], rtol=1e-4) assert_allclose(res.fun, 1.125, rtol=1e-4) class TestNewToOld(object): def test_multiple_constraint_objects(self): fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2 x0 = [2, 0, 1] coni = [] # only inequality constraints (can use cobyla) methods = ["slsqp", "cobyla", "trust-constr"] # mixed old and new coni.append([{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2}, NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)]) coni.append([LinearConstraint([1, -2, 0], -2, np.inf), NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)]) coni.append([NonlinearConstraint(lambda x: x[0] - 2 * x[1] + 2, 0, np.inf), NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)]) for con in coni: funs = {} for method in methods: with suppress_warnings() as sup: sup.filter(UserWarning) result = minimize(fun, x0, method=method, constraints=con) funs[method] = result.fun assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-4) assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-4) def test_individual_constraint_objects(self): fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2 x0 = [2, 0, 1] cone = [] # with equality constraints (can't use cobyla) coni = [] # only inequality constraints (can use cobyla) methods = ["slsqp", "cobyla", "trust-constr"] # nonstandard data types for constraint equality bounds cone.append(NonlinearConstraint(lambda x: x[0] - x[1], 1, 1)) cone.append(NonlinearConstraint(lambda x: x[0] - x[1], [1.21], [1.21])) cone.append(NonlinearConstraint(lambda x: x[0] - x[1], 1.21, np.array([1.21]))) # multiple equalities cone.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], 1.21, 1.21)) # two same equalities cone.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], [1.21, 1.4], [1.21, 1.4])) # two different equalities cone.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], [1.21, 1.21], 1.21)) # equality specified two ways cone.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], [1.21, -np.inf], [1.21, np.inf])) # equality + unbounded # nonstandard data types for constraint inequality bounds coni.append(NonlinearConstraint(lambda x: x[0] - x[1], 1.21, np.inf)) coni.append(NonlinearConstraint(lambda x: x[0] - x[1], [1.21], np.inf)) coni.append(NonlinearConstraint(lambda x: x[0] - x[1], 1.21, np.array([np.inf]))) coni.append(NonlinearConstraint(lambda x: x[0] - x[1], -np.inf, -3)) coni.append(NonlinearConstraint(lambda x: x[0] - x[1], np.array(-np.inf), -3)) # multiple inequalities/equalities coni.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], 1.21, np.inf)) # two same inequalities cone.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], [1.21, -np.inf], [1.21, 1.4])) # mixed equality/inequality coni.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], [1.1, .8], [1.2, 1.4])) # bounded above and below coni.append(NonlinearConstraint( lambda x: [x[0] - x[1], x[1] - x[2]], [-1.2, -1.4], [-1.1, -.8])) # - bounded above and below # quick check of LinearConstraint class (very little new code to test) cone.append(LinearConstraint([1, -1, 0], 1.21, 1.21)) cone.append(LinearConstraint([[1, -1, 0], [0, 1, -1]], 1.21, 1.21)) cone.append(LinearConstraint([[1, -1, 0], [0, 1, -1]], [1.21, -np.inf], [1.21, 1.4])) for con in coni: funs = {} for method in methods: with suppress_warnings() as sup: sup.filter(UserWarning) result = minimize(fun, x0, method=method, constraints=con) funs[method] = result.fun assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3) assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-3) for con in cone: funs = {} for method in methods[::2]: # skip cobyla with suppress_warnings() as sup: sup.filter(UserWarning) result = minimize(fun, x0, method=method, constraints=con) funs[method] = result.fun assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3) class TestNewToOldSLSQP(object): method = 'slsqp' elec = Elec(n_electrons=2) elec.x_opt = np.array([-0.58438468, 0.58438466, 0.73597047, -0.73597044, 0.34180668, -0.34180667]) brock = BoundedRosenbrock() brock.x_opt = [0, 0] list_of_problems = [Maratos(), HyperbolicIneq(), Rosenbrock(), IneqRosenbrock(), EqIneqRosenbrock(), elec, brock ] def test_list_of_problems(self): for prob in self.list_of_problems: with suppress_warnings() as sup: sup.filter(UserWarning) result = minimize(prob.fun, prob.x0, method=self.method, bounds=prob.bounds, constraints=prob.constr) assert_array_almost_equal(result.x, prob.x_opt, decimal=3) def test_warn_mixed_constraints(self): # warns about inefficiency of mixed equality/inequality constraints fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2 cons = NonlinearConstraint(lambda x: [x[0]**2 - x[1], x[1] - x[2]], [1.1, .8], [1.1, 1.4]) bnds = ((0, None), (0, None), (0, None)) with suppress_warnings() as sup: sup.filter(UserWarning, "delta_grad == 0.0") assert_warns(OptimizeWarning, minimize, fun, (2, 0, 1), method=self.method, bounds=bnds, constraints=cons) def test_warn_ignored_options(self): # warns about constraint options being ignored fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2 x0 = (2, 0, 1) if self.method == "slsqp": bnds = ((0, None), (0, None), (0, None)) else: bnds = None cons = NonlinearConstraint(lambda x: x[0], 2, np.inf) res = minimize(fun, x0, method=self.method, bounds=bnds, constraints=cons) # no warnings without constraint options assert_allclose(res.fun, 1) cons = LinearConstraint([1, 0, 0], 2, np.inf) res = minimize(fun, x0, method=self.method, bounds=bnds, constraints=cons) # no warnings without constraint options assert_allclose(res.fun, 1) cons = [] cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf, keep_feasible=True)) cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf, hess=BFGS())) cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf, finite_diff_jac_sparsity=42)) cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf, finite_diff_rel_step=42)) cons.append(LinearConstraint([1, 0, 0], 2, np.inf, keep_feasible=True)) for con in cons: assert_warns(OptimizeWarning, minimize, fun, x0, method=self.method, bounds=bnds, constraints=cons) class TestNewToOldCobyla(object): method = 'cobyla' list_of_problems = [ Elec(n_electrons=2), Elec(n_electrons=4), ] @pytest.mark.slow def test_list_of_problems(self): for prob in self.list_of_problems: with suppress_warnings() as sup: sup.filter(UserWarning) truth = minimize(prob.fun, prob.x0, method='trust-constr', bounds=prob.bounds, constraints=prob.constr) result = minimize(prob.fun, prob.x0, method=self.method, bounds=prob.bounds, constraints=prob.constr) assert_allclose(result.fun, truth.fun, rtol=1e-3)