| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228 |
- import logging
- import sys
- import warnings
- 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, OptimizeWarning)
- from scipy.optimize._constraints import new_constraint_to_old
- from scipy.optimize._shgo import SHGO
- from scipy.optimize.tests.test_minimize_constrained import MaratosTestArgs
- 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(10)
- def test_f5_3_cons_symmetry(self):
- """Asymmetrically 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)
- def test_args_gh23517(self):
- """
- Checks that using `args` for func, jac and hess works
- """
- obj = MaratosTestArgs("a", 234)
- obj.bounds = Bounds([-5, -5], [5, 5])
- res2 = minimize(
- obj.fun,
- [4.0, 4.0],
- constraints=obj.constr,
- bounds=obj.bounds,
- method='trust-constr',
- args=("a", 234),
- jac=obj.grad
- )
- assert_allclose(res2.x, obj.x_opt, atol=1e-6)
- obj = MaratosTestArgs("a", 234)
- obj.bounds = Bounds([-10., -10.], [10., 10.])
- with warnings.catch_warnings():
- # warnings are from initialization of NonlinearConstraint and
- # poor initialization of the constraint by shgo
- warnings.simplefilter(
- "ignore",
- (OptimizeWarning, RuntimeWarning)
- )
- res = shgo(
- func=obj.fun,
- bounds=obj.bounds,
- args=("a", 234),
- minimizer_kwargs={
- "method": 'trust-constr',
- "constraints": obj.constr,
- "bounds": obj.bounds,
- "jac": obj.grad,
- "args": ("a", 234),
- },
- constraints=obj.constr,
- sampling_method='sobol',
- )
- assert_allclose(res.x, obj.x_opt, atol=1e-6)
- res = shgo(
- func=obj.fun,
- bounds=obj.bounds,
- args=("a", 234),
- minimizer_kwargs={
- "method": 'trust-constr',
- "constraints": obj.constr,
- "bounds": obj.bounds,
- "jac": obj.grad,
- "hess": obj.hess,
- },
- constraints=obj.constr,
- sampling_method='sobol',
- )
- assert_allclose(res.x, obj.x_opt, atol=1e-6)
- @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(10)
- 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
- min_kwds = {"method": "SLSQP", "jac": True}
- opt_kwds = {"jac": True}
- shgo(
- func,
- bounds=[[-1, 1], [1, 2]],
- n=100, iters=5,
- sampling_method="sobol",
- minimizer_kwargs=min_kwds,
- options=opt_kwds
- )
- assert min_kwds['jac'] is True
- assert "jac" in opt_kwds
- # 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)
- # Testing the passing of jac via options dict
- res = shgo(func, bounds=bounds, sampling_method="sobol",
- minimizer_kwargs={'method': 'SLSQP'},
- options={'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
|