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

762 lines
26 KiB
Python

import logging
import numpy
import pytest
from pytest import raises as assert_raises, warns
from scipy.optimize import shgo
from scipy.optimize._shgo import SHGO
class StructTestFunction(object):
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 (type(g) is not tuple) and (type(g) is not 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 -(numpy.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 minimizer retrievals
"""
def f(self, x):
return (x - 30) * numpy.sin(x)
def g(x):
return 58 - numpy.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=numpy.array([[1.53567906],
[55.01782167],
[7.80894889],
[48.74797493],
[14.07445705],
[42.4913859],
[20.31743841],
[36.28607535],
[26.43039605],
[30.76371366]]),
expected_funl=numpy.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=numpy.array([[1.53567906]]),
expected_funl=numpy.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
"""
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)
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)
* numpy.sin(numpy.sqrt(abs(x[0] / 2.0 + (x[1] + 47.0))))
- x[0] * numpy.sin(numpy.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):
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=[-2.71247337e-08,
-2.71247337e-08,
-2.50000222e+00,
-2.71247337e-08,
-2.71247337e-08,
-1.50000222e+00]
)
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
)
def run_test(test, args=(), test_atol=1e-5, n=100, iters=None,
callback=None, minimizer_kwargs=None, options=None,
sampling_method='sobol'):
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)
logging.info(res)
if test.expected_x is not None:
numpy.testing.assert_allclose(res.x, test.expected_x,
rtol=test_atol,
atol=test_atol)
# (Optional tests)
if test.expected_fun is not None:
numpy.testing.assert_allclose(res.fun,
test.expected_fun,
atol=test_atol)
if test.expected_xl is not None:
numpy.testing.assert_allclose(res.xl,
test.expected_xl,
atol=test_atol)
if test.expected_funl is not None:
numpy.testing.assert_allclose(res.funl,
test.expected_funl,
atol=test_atol)
return
# Base test functions:
class TestShgoSobolTestFunctions(object):
"""
Global optimization 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)]"""
run_test(test1_3)
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)"""
# run_test(test4_1, n=500)
# run_test(test4_1, n=800)
options = {'infty_constraints': False}
run_test(test4_1, n=990, options=options)
def test_f5_1_sobol(self):
"""NLP: Eggholder, multimodal"""
run_test(test5_1, n=30)
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):
# """1-D tabletop function"""
# run_test(test11_1)
class TestShgoSimplicialTestFunctions(object):
"""
Global optimization 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=1, 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, 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(self):
"""LJ: Symmetry-constrained test function"""
options = {'symmetry': True,
'disp': True}
args = (6,) # Number of atoms
run_test(testLJ, args=args, n=None,
options=options, iters=4,
sampling_method='simplicial')
# Argument test functions
class TestShgoArguments(object):
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=7, 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')
numpy.testing.assert_allclose(res.x, test2_1.expected_x, rtol=1e-5,
atol=1e-5)
numpy.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})
@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 evalutions"""
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')
@pytest.mark.slow
def test_4_3_known_f_min(self):
"""Test Global mode limiting local evalutions"""
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,
'infty_constraints': False}
run_test(test4_1, n=300, test_atol=1e-5, options=options,
sampling_method='sobol')
def test_4_4_known_f_min(self):
"""Test Global mode limiting local evalutions for 1-D functions"""
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')
numpy.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)
numpy.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')
numpy.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')
numpy.testing.assert_allclose(res.x, test3_1.expected_x, rtol=1e-5,
atol=1e-5)
numpy.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')
numpy.testing.assert_allclose(res.x, test3_1.expected_x, rtol=1e-5,
atol=1e-5)
numpy.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', '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}
print("Solver = {}".format(solver))
print("=" * 100)
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 numpy.array([2 * x[0], 2 * x[1]]).T
def hess(x):
return numpy.array([[2, 0], [0, 2]])
minimizer_kwargs = {'method': solver,
'jac': jac,
'hess': hess}
logging.info("Solver = {}".format(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])
def test_10_finite_time(self):
"""Test single function constraint passing"""
options = {'maxtime': 1e-15}
shgo(test1_1.f, test1_1.bounds, n=1, iters=None,
options=options, sampling_method='sobol')
def test_11_f_min_time(self):
"""Test to cover the case where f_lowest == 0"""
options = {'maxtime': 1e-15,
'f_min': 0.0}
shgo(test1_2.f, test1_2.bounds, n=1, iters=None,
options=options, sampling_method='sobol')
def test_12_sobol_inf_cons(self):
"""Test to cover the case where f_lowest == 0"""
options = {'maxtime': 1e-15,
'f_min': 0.0}
shgo(test1_2.f, test1_2.bounds, n=1, iters=None,
options=options, sampling_method='sobol')
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)
SHGOc.sobol_points(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=30, 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):
"""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 numpy.random.uniform(size=(n,d))
run_test(test1_1, n=30, sampling_method=sample)
# Failure test functions
class TestShgoFailures(object):
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')
numpy.testing.assert_equal(False, res.success)
numpy.testing.assert_equal(4, res.nfev)
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,
'disp': True}
res = shgo(test_table.f, test_table.bounds, n=3, options=options,
sampling_method='sobol')
numpy.testing.assert_equal(False, res.success)
# numpy.testing.assert_equal(9, res.nfev)
numpy.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')
numpy.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')
numpy.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')
numpy.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')
numpy.testing.assert_equal(False, res.success)
def test_6_1_lower_known_f_min(self):
"""Test Global mode limiting local evalutions 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)