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.

267 lines
9.9 KiB
Python

5 years ago
# Dual annealing unit tests implementation.
# Copyright (c) 2018 Sylvain Gubian <sylvain.gubian@pmi.com>,
# Yang Xiang <yang.xiang@pmi.com>
# Author: Sylvain Gubian, PMP S.A.
"""
Unit tests for the dual annealing global optimizer
"""
from scipy.optimize import dual_annealing
from scipy.optimize._dual_annealing import VisitingDistribution
from scipy.optimize._dual_annealing import ObjectiveFunWrapper
from scipy.optimize._dual_annealing import EnergyState
from scipy.optimize._dual_annealing import LocalSearchWrapper
from scipy.optimize import rosen, rosen_der
import numpy as np
from numpy.testing import (assert_equal, TestCase, assert_allclose,
assert_array_less)
from pytest import raises as assert_raises
from scipy._lib._util import check_random_state
class TestDualAnnealing(TestCase):
def setUp(self):
# A function that returns always infinity for initialization tests
self.weirdfunc = lambda x: np.inf
# 2-D bounds for testing function
self.ld_bounds = [(-5.12, 5.12)] * 2
# 4-D bounds for testing function
self.hd_bounds = self.ld_bounds * 4
# Number of values to be generated for testing visit function
self.nbtestvalues = 5000
self.high_temperature = 5230
self.low_temperature = 0.1
self.qv = 2.62
self.seed = 1234
self.rs = check_random_state(self.seed)
self.nb_fun_call = 0
self.ngev = 0
def tearDown(self):
pass
def callback(self, x, f, context):
# For testing callback mechanism. Should stop for e <= 1 as
# the callback function returns True
if f <= 1.0:
return True
def func(self, x, args=()):
# Using Rastrigin function for performing tests
if args:
shift = args
else:
shift = 0
y = np.sum((x - shift) ** 2 - 10 * np.cos(2 * np.pi * (
x - shift))) + 10 * np.size(x) + shift
self.nb_fun_call += 1
return y
def rosen_der_wrapper(self, x, args=()):
self.ngev += 1
return rosen_der(x, *args)
def test_visiting_stepping(self):
lu = list(zip(*self.ld_bounds))
lower = np.array(lu[0])
upper = np.array(lu[1])
dim = lower.size
vd = VisitingDistribution(lower, upper, self.qv, self.rs)
values = np.zeros(dim)
x_step_low = vd.visiting(values, 0, self.high_temperature)
# Make sure that only the first component is changed
assert_equal(np.not_equal(x_step_low, 0), True)
values = np.zeros(dim)
x_step_high = vd.visiting(values, dim, self.high_temperature)
# Make sure that component other than at dim has changed
assert_equal(np.not_equal(x_step_high[0], 0), True)
def test_visiting_dist_high_temperature(self):
lu = list(zip(*self.ld_bounds))
lower = np.array(lu[0])
upper = np.array(lu[1])
vd = VisitingDistribution(lower, upper, self.qv, self.rs)
values = np.zeros(self.nbtestvalues)
for i in np.arange(self.nbtestvalues):
values[i] = vd.visit_fn(self.high_temperature)
# Visiting distribution is a distorted version of Cauchy-Lorentz
# distribution, and as no 1st and higher moments (no mean defined,
# no variance defined).
# Check that big tails values are generated
assert_array_less(np.min(values), 1e-10)
assert_array_less(1e+10, np.max(values))
def test_reset(self):
owf = ObjectiveFunWrapper(self.weirdfunc)
lu = list(zip(*self.ld_bounds))
lower = np.array(lu[0])
upper = np.array(lu[1])
es = EnergyState(lower, upper)
assert_raises(ValueError, es.reset, owf, check_random_state(None))
def test_low_dim(self):
ret = dual_annealing(
self.func, self.ld_bounds, seed=self.seed)
assert_allclose(ret.fun, 0., atol=1e-12)
def test_high_dim(self):
ret = dual_annealing(self.func, self.hd_bounds)
assert_allclose(ret.fun, 0., atol=1e-12)
def test_low_dim_no_ls(self):
ret = dual_annealing(self.func, self.ld_bounds,
no_local_search=True)
assert_allclose(ret.fun, 0., atol=1e-4)
def test_high_dim_no_ls(self):
ret = dual_annealing(self.func, self.hd_bounds,
no_local_search=True)
assert_allclose(ret.fun, 0., atol=1e-4)
def test_nb_fun_call(self):
ret = dual_annealing(self.func, self.ld_bounds)
assert_equal(self.nb_fun_call, ret.nfev)
def test_nb_fun_call_no_ls(self):
ret = dual_annealing(self.func, self.ld_bounds,
no_local_search=True)
assert_equal(self.nb_fun_call, ret.nfev)
def test_max_reinit(self):
assert_raises(ValueError, dual_annealing, self.weirdfunc,
self.ld_bounds)
def test_reproduce(self):
seed = 1234
res1 = dual_annealing(self.func, self.ld_bounds, seed=seed)
res2 = dual_annealing(self.func, self.ld_bounds, seed=seed)
res3 = dual_annealing(self.func, self.ld_bounds, seed=seed)
# If we have reproducible results, x components found has to
# be exactly the same, which is not the case with no seeding
assert_equal(res1.x, res2.x)
assert_equal(res1.x, res3.x)
def test_bounds_integrity(self):
wrong_bounds = [(-5.12, 5.12), (1, 0), (5.12, 5.12)]
assert_raises(ValueError, dual_annealing, self.func,
wrong_bounds)
def test_bound_validity(self):
invalid_bounds = [(-5, 5), (-np.inf, 0), (-5, 5)]
assert_raises(ValueError, dual_annealing, self.func,
invalid_bounds)
invalid_bounds = [(-5, 5), (0, np.inf), (-5, 5)]
assert_raises(ValueError, dual_annealing, self.func,
invalid_bounds)
invalid_bounds = [(-5, 5), (0, np.nan), (-5, 5)]
assert_raises(ValueError, dual_annealing, self.func,
invalid_bounds)
def test_max_fun_ls(self):
ret = dual_annealing(self.func, self.ld_bounds, maxfun=100)
ls_max_iter = min(max(
len(self.ld_bounds) * LocalSearchWrapper.LS_MAXITER_RATIO,
LocalSearchWrapper.LS_MAXITER_MIN),
LocalSearchWrapper.LS_MAXITER_MAX)
assert ret.nfev <= 100 + ls_max_iter
def test_max_fun_no_ls(self):
ret = dual_annealing(self.func, self.ld_bounds,
no_local_search=True, maxfun=500)
assert ret.nfev <= 500
def test_maxiter(self):
ret = dual_annealing(self.func, self.ld_bounds, maxiter=700)
assert ret.nit <= 700
# Testing that args are passed correctly for dual_annealing
def test_fun_args_ls(self):
ret = dual_annealing(self.func, self.ld_bounds,
args=((3.14159, )))
assert_allclose(ret.fun, 3.14159, atol=1e-6)
# Testing that args are passed correctly for pure simulated annealing
def test_fun_args_no_ls(self):
ret = dual_annealing(self.func, self.ld_bounds,
args=((3.14159, )), no_local_search=True)
assert_allclose(ret.fun, 3.14159, atol=1e-4)
def test_callback_stop(self):
# Testing that callback make the algorithm stop for
# fun value <= 1.0 (see callback method)
ret = dual_annealing(self.func, self.ld_bounds,
callback=self.callback)
assert ret.fun <= 1.0
assert 'stop early' in ret.message[0]
def test_neldermed_ls_minimizer(self):
minimizer_opts = {
'method': 'Nelder-Mead',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-6)
def test_powell_ls_minimizer(self):
minimizer_opts = {
'method': 'Powell',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-8)
def test_cg_ls_minimizer(self):
minimizer_opts = {
'method': 'CG',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-8)
def test_bfgs_ls_minimizer(self):
minimizer_opts = {
'method': 'BFGS',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-8)
def test_tnc_ls_minimizer(self):
minimizer_opts = {
'method': 'TNC',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-8)
def test_colyba_ls_minimizer(self):
minimizer_opts = {
'method': 'COBYLA',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-5)
def test_slsqp_ls_minimizer(self):
minimizer_opts = {
'method': 'SLSQP',
}
ret = dual_annealing(self.func, self.ld_bounds,
local_search_options=minimizer_opts)
assert_allclose(ret.fun, 0., atol=1e-7)
def test_wrong_restart_temp(self):
assert_raises(ValueError, dual_annealing, self.func,
self.ld_bounds, restart_temp_ratio=1)
assert_raises(ValueError, dual_annealing, self.func,
self.ld_bounds, restart_temp_ratio=0)
def test_gradient_gnev(self):
minimizer_opts = {
'jac': self.rosen_der_wrapper,
}
ret = dual_annealing(rosen, self.ld_bounds,
local_search_options=minimizer_opts)
assert ret.njev == self.ngev