You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1229 lines
41 KiB

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