import logging import sys import numpy as np import time from multiprocessing import Pool from numpy.testing import assert_allclose, IS_PYPY import pytest from pytest import raises as assert_raises, warns from scipy.optimize import (shgo, Bounds, minimize_scalar, minimize, rosen, rosen_der, rosen_hess, NonlinearConstraint) from scipy.optimize._constraints import new_constraint_to_old from scipy.optimize._shgo import SHGO class StructTestFunction: def __init__(self, bounds, expected_x, expected_fun=None, expected_xl=None, expected_funl=None): self.bounds = bounds self.expected_x = expected_x self.expected_fun = expected_fun self.expected_xl = expected_xl self.expected_funl = expected_funl def wrap_constraints(g): cons = [] if g is not None: if not isinstance(g, (tuple, list)): g = (g,) else: pass for g in g: cons.append({'type': 'ineq', 'fun': g}) cons = tuple(cons) else: cons = None return cons class StructTest1(StructTestFunction): def f(self, x): return x[0] ** 2 + x[1] ** 2 def g(x): return -(np.sum(x, axis=0) - 6.0) cons = wrap_constraints(g) test1_1 = StructTest1(bounds=[(-1, 6), (-1, 6)], expected_x=[0, 0]) test1_2 = StructTest1(bounds=[(0, 1), (0, 1)], expected_x=[0, 0]) test1_3 = StructTest1(bounds=[(None, None), (None, None)], expected_x=[0, 0]) class StructTest2(StructTestFunction): """ Scalar function with several minima to test all minimiser retrievals """ def f(self, x): return (x - 30) * np.sin(x) def g(x): return 58 - np.sum(x, axis=0) cons = wrap_constraints(g) test2_1 = StructTest2(bounds=[(0, 60)], expected_x=[1.53567906], expected_fun=-28.44677132, # Important: test that funl return is in the correct # order expected_xl=np.array([[1.53567906], [55.01782167], [7.80894889], [48.74797493], [14.07445705], [42.4913859], [20.31743841], [36.28607535], [26.43039605], [30.76371366]]), expected_funl=np.array([-28.44677132, -24.99785984, -22.16855376, -18.72136195, -15.89423937, -12.45154942, -9.63133158, -6.20801301, -3.43727232, -0.46353338]) ) test2_2 = StructTest2(bounds=[(0, 4.5)], expected_x=[1.53567906], expected_fun=[-28.44677132], expected_xl=np.array([[1.53567906]]), expected_funl=np.array([-28.44677132]) ) class StructTest3(StructTestFunction): """ Hock and Schittkowski 18 problem (HS18). Hoch and Schittkowski (1981) http://www.ai7.uni-bayreuth.de/test_problem_coll.pdf Minimize: f = 0.01 * (x_1)**2 + (x_2)**2 Subject to: x_1 * x_2 - 25.0 >= 0, (x_1)**2 + (x_2)**2 - 25.0 >= 0, 2 <= x_1 <= 50, 0 <= x_2 <= 50. Approx. Answer: f([(250)**0.5 , (2.5)**0.5]) = 5.0 """ # amended to test vectorisation of constraints def f(self, x): return 0.01 * (x[0]) ** 2 + (x[1]) ** 2 def g1(x): return x[0] * x[1] - 25.0 def g2(x): return x[0] ** 2 + x[1] ** 2 - 25.0 # g = (g1, g2) # cons = wrap_constraints(g) def g(x): return x[0] * x[1] - 25.0, x[0] ** 2 + x[1] ** 2 - 25.0 # this checks that shgo can be sent new-style constraints __nlc = NonlinearConstraint(g, 0, np.inf) cons = (__nlc,) test3_1 = StructTest3(bounds=[(2, 50), (0, 50)], expected_x=[250 ** 0.5, 2.5 ** 0.5], expected_fun=5.0 ) class StructTest4(StructTestFunction): """ Hock and Schittkowski 11 problem (HS11). Hoch and Schittkowski (1981) NOTE: Did not find in original reference to HS collection, refer to Henderson (2015) problem 7 instead. 02.03.2016 """ def f(self, x): return ((x[0] - 10) ** 2 + 5 * (x[1] - 12) ** 2 + x[2] ** 4 + 3 * (x[3] - 11) ** 2 + 10 * x[4] ** 6 + 7 * x[5] ** 2 + x[ 6] ** 4 - 4 * x[5] * x[6] - 10 * x[5] - 8 * x[6] ) def g1(x): return -(2 * x[0] ** 2 + 3 * x[1] ** 4 + x[2] + 4 * x[3] ** 2 + 5 * x[4] - 127) def g2(x): return -(7 * x[0] + 3 * x[1] + 10 * x[2] ** 2 + x[3] - x[4] - 282.0) def g3(x): return -(23 * x[0] + x[1] ** 2 + 6 * x[5] ** 2 - 8 * x[6] - 196) def g4(x): return -(4 * x[0] ** 2 + x[1] ** 2 - 3 * x[0] * x[1] + 2 * x[2] ** 2 + 5 * x[5] - 11 * x[6]) g = (g1, g2, g3, g4) cons = wrap_constraints(g) test4_1 = StructTest4(bounds=[(-10, 10), ] * 7, expected_x=[2.330499, 1.951372, -0.4775414, 4.365726, -0.6244870, 1.038131, 1.594227], expected_fun=680.6300573 ) class StructTest5(StructTestFunction): def f(self, x): return ( -(x[1] + 47.0)*np.sin(np.sqrt(abs(x[0]/2.0 + (x[1] + 47.0)))) - x[0]*np.sin(np.sqrt(abs(x[0] - (x[1] + 47.0)))) ) g = None cons = wrap_constraints(g) test5_1 = StructTest5(bounds=[(-512, 512), (-512, 512)], expected_fun=[-959.64066272085051], expected_x=[512., 404.23180542]) class StructTestLJ(StructTestFunction): """ LennardJones objective function. Used to test symmetry constraints settings. """ def f(self, x, *args): print(f'x = {x}') self.N = args[0] k = int(self.N / 3) s = 0.0 for i in range(k - 1): for j in range(i + 1, k): a = 3 * i b = 3 * j xd = x[a] - x[b] yd = x[a + 1] - x[b + 1] zd = x[a + 2] - x[b + 2] ed = xd * xd + yd * yd + zd * zd ud = ed * ed * ed if ed > 0.0: s += (1.0 / ud - 2.0) / ud return s g = None cons = wrap_constraints(g) N = 6 boundsLJ = list(zip([-4.0] * 6, [4.0] * 6)) testLJ = StructTestLJ(bounds=boundsLJ, expected_fun=[-1.0], expected_x=None, # expected_x=[-2.71247337e-08, # -2.71247337e-08, # -2.50000222e+00, # -2.71247337e-08, # -2.71247337e-08, # -1.50000222e+00] ) class StructTestS(StructTestFunction): def f(self, x): return ((x[0] - 0.5) ** 2 + (x[1] - 0.5) ** 2 + (x[2] - 0.5) ** 2 + (x[3] - 0.5) ** 2) g = None cons = wrap_constraints(g) test_s = StructTestS(bounds=[(0, 2.0), ] * 4, expected_fun=0.0, expected_x=np.ones(4) - 0.5 ) class StructTestTable(StructTestFunction): def f(self, x): if x[0] == 3.0 and x[1] == 3.0: return 50 else: return 100 g = None cons = wrap_constraints(g) test_table = StructTestTable(bounds=[(-10, 10), (-10, 10)], expected_fun=[50], expected_x=[3.0, 3.0]) class StructTestInfeasible(StructTestFunction): """ Test function with no feasible domain. """ def f(self, x, *args): return x[0] ** 2 + x[1] ** 2 def g1(x): return x[0] + x[1] - 1 def g2(x): return -(x[0] + x[1] - 1) def g3(x): return -x[0] + x[1] - 1 def g4(x): return -(-x[0] + x[1] - 1) g = (g1, g2, g3, g4) cons = wrap_constraints(g) test_infeasible = StructTestInfeasible(bounds=[(2, 50), (-1, 1)], expected_fun=None, expected_x=None ) @pytest.mark.skip("Not a test") def run_test(test, args=(), test_atol=1e-5, n=100, iters=None, callback=None, minimizer_kwargs=None, options=None, sampling_method='sobol', workers=1): res = shgo(test.f, test.bounds, args=args, constraints=test.cons, n=n, iters=iters, callback=callback, minimizer_kwargs=minimizer_kwargs, options=options, sampling_method=sampling_method, workers=workers) print(f'res = {res}') logging.info(f'res = {res}') if test.expected_x is not None: np.testing.assert_allclose(res.x, test.expected_x, rtol=test_atol, atol=test_atol) # (Optional tests) if test.expected_fun is not None: np.testing.assert_allclose(res.fun, test.expected_fun, atol=test_atol) if test.expected_xl is not None: np.testing.assert_allclose(res.xl, test.expected_xl, atol=test_atol) if test.expected_funl is not None: np.testing.assert_allclose(res.funl, test.expected_funl, atol=test_atol) return # Base test functions: class TestShgoSobolTestFunctions: """ Global optimisation tests with Sobol sampling: """ # Sobol algorithm def test_f1_1_sobol(self): """Multivariate test function 1: x[0]**2 + x[1]**2 with bounds=[(-1, 6), (-1, 6)]""" run_test(test1_1) def test_f1_2_sobol(self): """Multivariate test function 1: x[0]**2 + x[1]**2 with bounds=[(0, 1), (0, 1)]""" run_test(test1_2) def test_f1_3_sobol(self): """Multivariate test function 1: x[0]**2 + x[1]**2 with bounds=[(None, None),(None, None)]""" options = {'disp': True} run_test(test1_3, options=options) def test_f2_1_sobol(self): """Univariate test function on f(x) = (x - 30) * sin(x) with bounds=[(0, 60)]""" run_test(test2_1) def test_f2_2_sobol(self): """Univariate test function on f(x) = (x - 30) * sin(x) bounds=[(0, 4.5)]""" run_test(test2_2) def test_f3_sobol(self): """NLP: Hock and Schittkowski problem 18""" run_test(test3_1) @pytest.mark.slow def test_f4_sobol(self): """NLP: (High dimensional) Hock and Schittkowski 11 problem (HS11)""" options = {'infty_constraints': False} # run_test(test4_1, n=990, options=options) run_test(test4_1, n=990 * 2, options=options) def test_f5_1_sobol(self): """NLP: Eggholder, multimodal""" # run_test(test5_1, n=30) run_test(test5_1, n=60) def test_f5_2_sobol(self): """NLP: Eggholder, multimodal""" # run_test(test5_1, n=60, iters=5) run_test(test5_1, n=60, iters=5) # def test_t911(self): # """1D tabletop function""" # run_test(test11_1) class TestShgoSimplicialTestFunctions: """ Global optimisation tests with Simplicial sampling: """ def test_f1_1_simplicial(self): """Multivariate test function 1: x[0]**2 + x[1]**2 with bounds=[(-1, 6), (-1, 6)]""" run_test(test1_1, n=1, sampling_method='simplicial') def test_f1_2_simplicial(self): """Multivariate test function 1: x[0]**2 + x[1]**2 with bounds=[(0, 1), (0, 1)]""" run_test(test1_2, n=1, sampling_method='simplicial') def test_f1_3_simplicial(self): """Multivariate test function 1: x[0]**2 + x[1]**2 with bounds=[(None, None),(None, None)]""" run_test(test1_3, n=5, sampling_method='simplicial') def test_f2_1_simplicial(self): """Univariate test function on f(x) = (x - 30) * sin(x) with bounds=[(0, 60)]""" options = {'minimize_every_iter': False} run_test(test2_1, n=200, iters=7, options=options, sampling_method='simplicial') def test_f2_2_simplicial(self): """Univariate test function on f(x) = (x - 30) * sin(x) bounds=[(0, 4.5)]""" run_test(test2_2, n=1, sampling_method='simplicial') def test_f3_simplicial(self): """NLP: Hock and Schittkowski problem 18""" run_test(test3_1, n=1, sampling_method='simplicial') @pytest.mark.slow def test_f4_simplicial(self): """NLP: (High dimensional) Hock and Schittkowski 11 problem (HS11)""" run_test(test4_1, n=1, sampling_method='simplicial') def test_lj_symmetry_old(self): """LJ: Symmetry-constrained test function""" options = {'symmetry': True, 'disp': True} args = (6,) # Number of atoms run_test(testLJ, args=args, n=300, options=options, iters=1, sampling_method='simplicial') def test_f5_1_lj_symmetry(self): """LJ: Symmetry constrained test function""" options = {'symmetry': [0, ] * 6, 'disp': True} args = (6,) # No. of atoms run_test(testLJ, args=args, n=300, options=options, iters=1, sampling_method='simplicial') def test_f5_2_cons_symmetry(self): """Symmetry constrained test function""" options = {'symmetry': [0, 0], 'disp': True} run_test(test1_1, n=200, options=options, iters=1, sampling_method='simplicial') @pytest.mark.fail_slow(5) def test_f5_3_cons_symmetry(self): """Assymmetrically constrained test function""" options = {'symmetry': [0, 0, 0, 3], 'disp': True} run_test(test_s, n=10000, options=options, iters=1, sampling_method='simplicial') @pytest.mark.skip("Not a test") def test_f0_min_variance(self): """Return a minimum on a perfectly symmetric problem, based on gh10429""" avg = 0.5 # Given average value of x cons = {'type': 'eq', 'fun': lambda x: np.mean(x) - avg} # Minimize the variance of x under the given constraint res = shgo(np.var, bounds=6 * [(0, 1)], constraints=cons) assert res.success assert_allclose(res.fun, 0, atol=1e-15) assert_allclose(res.x, 0.5) @pytest.mark.skip("Not a test") def test_f0_min_variance_1D(self): """Return a minimum on a perfectly symmetric 1D problem, based on gh10538""" def fun(x): return x * (x - 1.0) * (x - 0.5) bounds = [(0, 1)] res = shgo(fun, bounds=bounds) ref = minimize_scalar(fun, bounds=bounds[0]) assert res.success assert_allclose(res.fun, ref.fun) assert_allclose(res.x, ref.x, rtol=1e-6) # Argument test functions class TestShgoArguments: def test_1_1_simpl_iter(self): """Iterative simplicial sampling on TestFunction 1 (multivariate)""" run_test(test1_2, n=None, iters=2, sampling_method='simplicial') def test_1_2_simpl_iter(self): """Iterative simplicial on TestFunction 2 (univariate)""" options = {'minimize_every_iter': False} run_test(test2_1, n=None, iters=9, options=options, sampling_method='simplicial') def test_2_1_sobol_iter(self): """Iterative Sobol sampling on TestFunction 1 (multivariate)""" run_test(test1_2, n=None, iters=1, sampling_method='sobol') def test_2_2_sobol_iter(self): """Iterative Sobol sampling on TestFunction 2 (univariate)""" res = shgo(test2_1.f, test2_1.bounds, constraints=test2_1.cons, n=None, iters=1, sampling_method='sobol') np.testing.assert_allclose(res.x, test2_1.expected_x, rtol=1e-5, atol=1e-5) np.testing.assert_allclose(res.fun, test2_1.expected_fun, atol=1e-5) def test_3_1_disp_simplicial(self): """Iterative sampling on TestFunction 1 and 2 (multi and univariate) """ def callback_func(x): print("Local minimization callback test") for test in [test1_1, test2_1]: shgo(test.f, test.bounds, iters=1, sampling_method='simplicial', callback=callback_func, options={'disp': True}) shgo(test.f, test.bounds, n=1, sampling_method='simplicial', callback=callback_func, options={'disp': True}) def test_3_2_disp_sobol(self): """Iterative sampling on TestFunction 1 and 2 (multi and univariate)""" def callback_func(x): print("Local minimization callback test") for test in [test1_1, test2_1]: shgo(test.f, test.bounds, iters=1, sampling_method='sobol', callback=callback_func, options={'disp': True}) shgo(test.f, test.bounds, n=1, sampling_method='simplicial', callback=callback_func, options={'disp': True}) def test_args_gh14589(self): """Using `args` used to cause `shgo` to fail; see #14589, #15986, #16506""" res = shgo(func=lambda x, y, z: x * z + y, bounds=[(0, 3)], args=(1, 2) ) ref = shgo(func=lambda x: 2 * x + 1, bounds=[(0, 3)]) assert_allclose(res.fun, ref.fun) assert_allclose(res.x, ref.x) @pytest.mark.slow def test_4_1_known_f_min(self): """Test known function minima stopping criteria""" # Specify known function value options = {'f_min': test4_1.expected_fun, 'f_tol': 1e-6, 'minimize_every_iter': True} # TODO: Make default n higher for faster tests run_test(test4_1, n=None, test_atol=1e-5, options=options, sampling_method='simplicial') @pytest.mark.slow def test_4_2_known_f_min(self): """Test Global mode limiting local evaluations""" options = { # Specify known function value 'f_min': test4_1.expected_fun, 'f_tol': 1e-6, # Specify number of local iterations to perform 'minimize_every_iter': True, 'local_iter': 1} run_test(test4_1, n=None, test_atol=1e-5, options=options, sampling_method='simplicial') def test_4_4_known_f_min(self): """Test Global mode limiting local evaluations for 1D funcs""" options = { # Specify known function value 'f_min': test2_1.expected_fun, 'f_tol': 1e-6, # Specify number of local iterations to perform+ 'minimize_every_iter': True, 'local_iter': 1, 'infty_constraints': False} res = shgo(test2_1.f, test2_1.bounds, constraints=test2_1.cons, n=None, iters=None, options=options, sampling_method='sobol') np.testing.assert_allclose(res.x, test2_1.expected_x, rtol=1e-5, atol=1e-5) def test_5_1_simplicial_argless(self): """Test Default simplicial sampling settings on TestFunction 1""" res = shgo(test1_1.f, test1_1.bounds, constraints=test1_1.cons) np.testing.assert_allclose(res.x, test1_1.expected_x, rtol=1e-5, atol=1e-5) def test_5_2_sobol_argless(self): """Test Default sobol sampling settings on TestFunction 1""" res = shgo(test1_1.f, test1_1.bounds, constraints=test1_1.cons, sampling_method='sobol') np.testing.assert_allclose(res.x, test1_1.expected_x, rtol=1e-5, atol=1e-5) def test_6_1_simplicial_max_iter(self): """Test that maximum iteration option works on TestFunction 3""" options = {'max_iter': 2} res = shgo(test3_1.f, test3_1.bounds, constraints=test3_1.cons, options=options, sampling_method='simplicial') np.testing.assert_allclose(res.x, test3_1.expected_x, rtol=1e-5, atol=1e-5) np.testing.assert_allclose(res.fun, test3_1.expected_fun, atol=1e-5) def test_6_2_simplicial_min_iter(self): """Test that maximum iteration option works on TestFunction 3""" options = {'min_iter': 2} res = shgo(test3_1.f, test3_1.bounds, constraints=test3_1.cons, options=options, sampling_method='simplicial') np.testing.assert_allclose(res.x, test3_1.expected_x, rtol=1e-5, atol=1e-5) np.testing.assert_allclose(res.fun, test3_1.expected_fun, atol=1e-5) def test_7_1_minkwargs(self): """Test the minimizer_kwargs arguments for solvers with constraints""" # Test solvers for solver in ['COBYLA', 'COBYQA', 'SLSQP']: # Note that passing global constraints to SLSQP is tested in other # unittests which run test4_1 normally minimizer_kwargs = {'method': solver, 'constraints': test3_1.cons} run_test(test3_1, n=100, test_atol=1e-3, minimizer_kwargs=minimizer_kwargs, sampling_method='sobol') def test_7_2_minkwargs(self): """Test the minimizer_kwargs default inits""" minimizer_kwargs = {'ftol': 1e-5} options = {'disp': True} # For coverage purposes SHGO(test3_1.f, test3_1.bounds, constraints=test3_1.cons[0], minimizer_kwargs=minimizer_kwargs, options=options) def test_7_3_minkwargs(self): """Test minimizer_kwargs arguments for solvers without constraints""" for solver in ['Nelder-Mead', 'Powell', 'CG', 'BFGS', 'Newton-CG', 'L-BFGS-B', 'TNC', 'dogleg', 'trust-ncg', 'trust-exact', 'trust-krylov']: def jac(x): return np.array([2 * x[0], 2 * x[1]]).T def hess(x): return np.array([[2, 0], [0, 2]]) minimizer_kwargs = {'method': solver, 'jac': jac, 'hess': hess} logging.info(f"Solver = {solver}") logging.info("=" * 100) run_test(test1_1, n=100, test_atol=1e-3, minimizer_kwargs=minimizer_kwargs, sampling_method='sobol') def test_8_homology_group_diff(self): options = {'minhgrd': 1, 'minimize_every_iter': True} run_test(test1_1, n=None, iters=None, options=options, sampling_method='simplicial') def test_9_cons_g(self): """Test single function constraint passing""" SHGO(test3_1.f, test3_1.bounds, constraints=test3_1.cons[0]) @pytest.mark.xfail(IS_PYPY and sys.platform == 'win32', reason="Failing and fix in PyPy not planned (see gh-18632)") def test_10_finite_time(self): """Test single function constraint passing""" options = {'maxtime': 1e-15} def f(x): time.sleep(1e-14) return 0.0 res = shgo(f, test1_1.bounds, iters=5, options=options) # Assert that only 1 rather than 5 requested iterations ran: assert res.nit == 1 def test_11_f_min_0(self): """Test to cover the case where f_lowest == 0""" options = {'f_min': 0.0, 'disp': True} res = shgo(test1_2.f, test1_2.bounds, n=10, iters=None, options=options, sampling_method='sobol') np.testing.assert_equal(0, res.x[0]) np.testing.assert_equal(0, res.x[1]) # @nottest @pytest.mark.skip(reason="no way of currently testing this") def test_12_sobol_inf_cons(self): """Test to cover the case where f_lowest == 0""" # TODO: This test doesn't cover anything new, it is unknown what the # original test was intended for as it was never complete. Delete or # replace in the future. options = {'maxtime': 1e-15, 'f_min': 0.0} res = shgo(test1_2.f, test1_2.bounds, n=1, iters=None, options=options, sampling_method='sobol') np.testing.assert_equal(0.0, res.fun) def test_13_high_sobol(self): """Test init of high-dimensional sobol sequences""" def f(x): return 0 bounds = [(None, None), ] * 41 SHGOc = SHGO(f, bounds, sampling_method='sobol') # SHGOc.sobol_points(2, 50) SHGOc.sampling_function(2, 50) def test_14_local_iter(self): """Test limited local iterations for a pseudo-global mode""" options = {'local_iter': 4} run_test(test5_1, n=60, options=options) def test_15_min_every_iter(self): """Test minimize every iter options and cover function cache""" options = {'minimize_every_iter': True} run_test(test1_1, n=1, iters=7, options=options, sampling_method='sobol') def test_16_disp_bounds_minimizer(self, capsys): """Test disp=True with minimizers that do not support bounds """ options = {'disp': True} minimizer_kwargs = {'method': 'nelder-mead'} run_test(test1_2, sampling_method='simplicial', options=options, minimizer_kwargs=minimizer_kwargs) def test_17_custom_sampling(self): """Test the functionality to add custom sampling methods to shgo""" def sample(n, d): return np.random.uniform(size=(n, d)) run_test(test1_1, n=30, sampling_method=sample) def test_18_bounds_class(self): # test that new and old bounds yield same result def f(x): return np.square(x).sum() lb = [-6., 1., -5.] ub = [-1., 3., 5.] bounds_old = list(zip(lb, ub)) bounds_new = Bounds(lb, ub) res_old_bounds = shgo(f, bounds_old) res_new_bounds = shgo(f, bounds_new) assert res_new_bounds.nfev == res_old_bounds.nfev assert res_new_bounds.message == res_old_bounds.message assert res_new_bounds.success == res_old_bounds.success x_opt = np.array([-1., 1., 0.]) np.testing.assert_allclose(res_new_bounds.x, x_opt) np.testing.assert_allclose(res_new_bounds.x, res_old_bounds.x) @pytest.mark.fail_slow(5) def test_19_parallelization(self): """Test the functionality to add custom sampling methods to shgo""" with Pool(2) as p: run_test(test1_1, n=30, workers=p.map) # Constrained run_test(test1_1, n=30, workers=map) # Constrained with Pool(2) as p: run_test(test_s, n=30, workers=p.map) # Unconstrained run_test(test_s, n=30, workers=map) # Unconstrained def test_20_constrained_args(self): """Test that constraints can be passed to arguments""" def eggholder(x): return ( -(x[1] + 47.0)*np.sin(np.sqrt(abs(x[0] / 2.0 + (x[1] + 47.0)))) - x[0]*np.sin(np.sqrt(abs(x[0] - (x[1] + 47.0)))) ) def f(x): # (cattle-feed) return 24.55 * x[0] + 26.75 * x[1] + 39 * x[2] + 40.50 * x[3] bounds = [(0, 1.0), ] * 4 def g1_modified(x, i): return i * 2.3 * x[0] + i * 5.6 * x[1] + 11.1 * x[2] + 1.3 * x[ 3] - 5 # >=0 def g2(x): return ( 12*x[0] + 11.9*x[1] + 41.8*x[2] + 52.1*x[3] - 21 - 1.645*np.sqrt( 0.28*x[0]**2 + 0.19*x[1]**2 + 20.5*x[2]**2 + 0.62*x[3]**2 ) ) # >=0 def h1(x): return x[0] + x[1] + x[2] + x[3] - 1 # == 0 cons = ({'type': 'ineq', 'fun': g1_modified, "args": (0,)}, {'type': 'ineq', 'fun': g2}, {'type': 'eq', 'fun': h1}) shgo(f, bounds, n=300, iters=1, constraints=cons) # using constrain with arguments AND sampling method sobol shgo(f, bounds, n=300, iters=1, constraints=cons, sampling_method='sobol') def test_21_1_jac_true(self): """Test that shgo can handle objective functions that return the gradient alongside the objective value. Fixes gh-13547""" # previous def func(x): return np.sum(np.power(x, 2)), 2 * x shgo( func, bounds=[[-1, 1], [1, 2]], n=100, iters=5, sampling_method="sobol", minimizer_kwargs={'method': 'SLSQP', 'jac': True} ) # new def func(x): return np.sum(x ** 2), 2 * x bounds = [[-1, 1], [1, 2], [-1, 1], [1, 2], [0, 3]] res = shgo(func, bounds=bounds, sampling_method="sobol", minimizer_kwargs={'method': 'SLSQP', 'jac': True}) ref = minimize(func, x0=[1, 1, 1, 1, 1], bounds=bounds, jac=True) assert res.success assert_allclose(res.fun, ref.fun) assert_allclose(res.x, ref.x, atol=1e-15) @pytest.mark.parametrize('derivative', ['jac', 'hess', 'hessp']) def test_21_2_derivative_options(self, derivative): """shgo used to raise an error when passing `options` with 'jac' # see gh-12963. check that this is resolved """ def objective(x): return 3 * x[0] * x[0] + 2 * x[0] + 5 def gradient(x): return 6 * x[0] + 2 def hess(x): return 6 def hessp(x, p): return 6 * p derivative_funcs = {'jac': gradient, 'hess': hess, 'hessp': hessp} options = {derivative: derivative_funcs[derivative]} minimizer_kwargs = {'method': 'trust-constr'} bounds = [(-100, 100)] res = shgo(objective, bounds, minimizer_kwargs=minimizer_kwargs, options=options) ref = minimize(objective, x0=[0], bounds=bounds, **minimizer_kwargs, **options) assert res.success np.testing.assert_allclose(res.fun, ref.fun) np.testing.assert_allclose(res.x, ref.x) def test_21_3_hess_options_rosen(self): """Ensure the Hessian gets passed correctly to the local minimizer routine. Previous report gh-14533. """ bounds = [(0, 1.6), (0, 1.6), (0, 1.4), (0, 1.4), (0, 1.4)] options = {'jac': rosen_der, 'hess': rosen_hess} minimizer_kwargs = {'method': 'Newton-CG'} res = shgo(rosen, bounds, minimizer_kwargs=minimizer_kwargs, options=options) ref = minimize(rosen, np.zeros(5), method='Newton-CG', **options) assert res.success assert_allclose(res.fun, ref.fun) assert_allclose(res.x, ref.x, atol=1e-15) def test_21_arg_tuple_sobol(self): """shgo used to raise an error when passing `args` with Sobol sampling # see gh-12114. check that this is resolved""" def fun(x, k): return x[0] ** k constraints = ({'type': 'ineq', 'fun': lambda x: x[0] - 1}) bounds = [(0, 10)] res = shgo(fun, bounds, args=(1,), constraints=constraints, sampling_method='sobol') ref = minimize(fun, np.zeros(1), bounds=bounds, args=(1,), constraints=constraints) assert res.success assert_allclose(res.fun, ref.fun) assert_allclose(res.x, ref.x) # Failure test functions class TestShgoFailures: def test_1_maxiter(self): """Test failure on insufficient iterations""" options = {'maxiter': 2} res = shgo(test4_1.f, test4_1.bounds, n=2, iters=None, options=options, sampling_method='sobol') np.testing.assert_equal(False, res.success) # np.testing.assert_equal(4, res.nfev) np.testing.assert_equal(4, res.tnev) def test_2_sampling(self): """Rejection of unknown sampling method""" assert_raises(ValueError, shgo, test1_1.f, test1_1.bounds, sampling_method='not_Sobol') def test_3_1_no_min_pool_sobol(self): """Check that the routine stops when no minimiser is found after maximum specified function evaluations""" options = {'maxfev': 10, # 'maxev': 10, 'disp': True} res = shgo(test_table.f, test_table.bounds, n=3, options=options, sampling_method='sobol') np.testing.assert_equal(False, res.success) # np.testing.assert_equal(9, res.nfev) np.testing.assert_equal(12, res.nfev) def test_3_2_no_min_pool_simplicial(self): """Check that the routine stops when no minimiser is found after maximum specified sampling evaluations""" options = {'maxev': 10, 'disp': True} res = shgo(test_table.f, test_table.bounds, n=3, options=options, sampling_method='simplicial') np.testing.assert_equal(False, res.success) def test_4_1_bound_err(self): """Specified bounds ub > lb""" bounds = [(6, 3), (3, 5)] assert_raises(ValueError, shgo, test1_1.f, bounds) def test_4_2_bound_err(self): """Specified bounds are of the form (lb, ub)""" bounds = [(3, 5, 5), (3, 5)] assert_raises(ValueError, shgo, test1_1.f, bounds) def test_5_1_1_infeasible_sobol(self): """Ensures the algorithm terminates on infeasible problems after maxev is exceeded. Use infty constraints option""" options = {'maxev': 100, 'disp': True} res = shgo(test_infeasible.f, test_infeasible.bounds, constraints=test_infeasible.cons, n=100, options=options, sampling_method='sobol') np.testing.assert_equal(False, res.success) def test_5_1_2_infeasible_sobol(self): """Ensures the algorithm terminates on infeasible problems after maxev is exceeded. Do not use infty constraints option""" options = {'maxev': 100, 'disp': True, 'infty_constraints': False} res = shgo(test_infeasible.f, test_infeasible.bounds, constraints=test_infeasible.cons, n=100, options=options, sampling_method='sobol') np.testing.assert_equal(False, res.success) def test_5_2_infeasible_simplicial(self): """Ensures the algorithm terminates on infeasible problems after maxev is exceeded.""" options = {'maxev': 1000, 'disp': False} res = shgo(test_infeasible.f, test_infeasible.bounds, constraints=test_infeasible.cons, n=100, options=options, sampling_method='simplicial') np.testing.assert_equal(False, res.success) def test_6_1_lower_known_f_min(self): """Test Global mode limiting local evaluations with f* too high""" options = { # Specify known function value 'f_min': test2_1.expected_fun + 2.0, 'f_tol': 1e-6, # Specify number of local iterations to perform+ 'minimize_every_iter': True, 'local_iter': 1, 'infty_constraints': False} args = (test2_1.f, test2_1.bounds) kwargs = {'constraints': test2_1.cons, 'n': None, 'iters': None, 'options': options, 'sampling_method': 'sobol' } warns(UserWarning, shgo, *args, **kwargs) def test(self): from scipy.optimize import rosen, shgo bounds = [(0, 2), (0, 2), (0, 2), (0, 2), (0, 2)] def fun(x): fun.nfev += 1 return rosen(x) fun.nfev = 0 result = shgo(fun, bounds) print(result.x, result.fun, fun.nfev) # 50 # Returns class TestShgoReturns: def test_1_nfev_simplicial(self): bounds = [(0, 2), (0, 2), (0, 2), (0, 2), (0, 2)] def fun(x): fun.nfev += 1 return rosen(x) fun.nfev = 0 result = shgo(fun, bounds) np.testing.assert_equal(fun.nfev, result.nfev) def test_1_nfev_sobol(self): bounds = [(0, 2), (0, 2), (0, 2), (0, 2), (0, 2)] def fun(x): fun.nfev += 1 return rosen(x) fun.nfev = 0 result = shgo(fun, bounds, sampling_method='sobol') np.testing.assert_equal(fun.nfev, result.nfev) def test_vector_constraint(): # gh15514 def quad(x): x = np.asarray(x) return [np.sum(x ** 2)] nlc = NonlinearConstraint(quad, [2.2], [3]) oldc = new_constraint_to_old(nlc, np.array([1.0, 1.0])) res = shgo(rosen, [(0, 10), (0, 10)], constraints=oldc, sampling_method='sobol') assert np.all(np.sum((res.x)**2) >= 2.2) assert np.all(np.sum((res.x) ** 2) <= 3.0) assert res.success @pytest.mark.filterwarnings("ignore:delta_grad") def test_trust_constr(): def quad(x): x = np.asarray(x) return [np.sum(x ** 2)] nlc = NonlinearConstraint(quad, [2.6], [3]) minimizer_kwargs = {'method': 'trust-constr'} # note that we don't supply the constraints in minimizer_kwargs, # so if the final result obeys the constraints we know that shgo # passed them on to 'trust-constr' res = shgo( rosen, [(0, 10), (0, 10)], constraints=nlc, sampling_method='sobol', minimizer_kwargs=minimizer_kwargs ) assert np.all(np.sum((res.x)**2) >= 2.6) assert np.all(np.sum((res.x) ** 2) <= 3.0) assert res.success def test_equality_constraints(): # gh16260 bounds = [(0.9, 4.0)] * 2 # Constrain probabilities to 0 and 1. def faulty(x): return x[0] + x[1] nlc = NonlinearConstraint(faulty, 3.9, 3.9) res = shgo(rosen, bounds=bounds, constraints=nlc) assert_allclose(np.sum(res.x), 3.9) def faulty(x): return x[0] + x[1] - 3.9 constraints = {'type': 'eq', 'fun': faulty} res = shgo(rosen, bounds=bounds, constraints=constraints) assert_allclose(np.sum(res.x), 3.9) bounds = [(0, 1.0)] * 4 # sum of variable should equal 1. def faulty(x): return x[0] + x[1] + x[2] + x[3] - 1 # options = {'minimize_every_iter': True, 'local_iter':10} constraints = {'type': 'eq', 'fun': faulty} res = shgo( lambda x: - np.prod(x), bounds=bounds, constraints=constraints, sampling_method='sobol' ) assert_allclose(np.sum(res.x), 1.0) def test_gh16971(): def cons(x): return np.sum(x**2) - 0 c = {'fun': cons, 'type': 'ineq'} minimizer_kwargs = { 'method': 'COBYLA', 'options': {'rhobeg': 5, 'tol': 5e-1, 'catol': 0.05} } s = SHGO( rosen, [(0, 10)]*2, constraints=c, minimizer_kwargs=minimizer_kwargs ) assert s.minimizer_kwargs['method'].lower() == 'cobyla' assert s.minimizer_kwargs['options']['catol'] == 0.05