247 lines
7.8 KiB
Python
247 lines
7.8 KiB
Python
|
import numpy as np
|
||
|
import pytest
|
||
|
from numpy.testing import assert_allclose, assert_equal
|
||
|
|
||
|
from scipy.optimize import (
|
||
|
Bounds,
|
||
|
LinearConstraint,
|
||
|
NonlinearConstraint,
|
||
|
OptimizeResult,
|
||
|
minimize,
|
||
|
)
|
||
|
|
||
|
|
||
|
class TestCOBYQA:
|
||
|
|
||
|
def setup_method(self):
|
||
|
self.x0 = [4.95, 0.66]
|
||
|
self.options = {'maxfev': 100}
|
||
|
|
||
|
@staticmethod
|
||
|
def fun(x, c=1.0):
|
||
|
return x[0]**2 + c * abs(x[1])**3
|
||
|
|
||
|
@staticmethod
|
||
|
def con(x):
|
||
|
return x[0]**2 + x[1]**2 - 25.0
|
||
|
|
||
|
def test_minimize_simple(self):
|
||
|
class Callback:
|
||
|
def __init__(self):
|
||
|
self.n_calls = 0
|
||
|
|
||
|
def __call__(self, x):
|
||
|
assert isinstance(x, np.ndarray)
|
||
|
self.n_calls += 1
|
||
|
|
||
|
class CallbackNewSyntax:
|
||
|
def __init__(self):
|
||
|
self.n_calls = 0
|
||
|
|
||
|
def __call__(self, intermediate_result):
|
||
|
assert isinstance(intermediate_result, OptimizeResult)
|
||
|
self.n_calls += 1
|
||
|
|
||
|
callback = Callback()
|
||
|
callback_new_syntax = CallbackNewSyntax()
|
||
|
|
||
|
# Minimize with method='cobyqa'.
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
callback=callback,
|
||
|
options=self.options,
|
||
|
)
|
||
|
sol_new = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
callback=callback_new_syntax,
|
||
|
options=self.options,
|
||
|
)
|
||
|
solution = [np.sqrt(25.0 - 4.0 / 9.0), 2.0 / 3.0]
|
||
|
assert_allclose(sol.x, solution, atol=1e-4)
|
||
|
assert sol.success, sol.message
|
||
|
assert sol.maxcv < 1e-8, sol
|
||
|
assert sol.nfev <= 100, sol
|
||
|
assert sol.fun < self.fun(solution) + 1e-3, sol
|
||
|
assert sol.nfev == callback.n_calls, \
|
||
|
"Callback is not called exactly once for every function eval."
|
||
|
assert_equal(sol.x, sol_new.x)
|
||
|
assert sol_new.success, sol_new.message
|
||
|
assert sol.fun == sol_new.fun
|
||
|
assert sol.maxcv == sol_new.maxcv
|
||
|
assert sol.nfev == sol_new.nfev
|
||
|
assert sol.nit == sol_new.nit
|
||
|
assert sol_new.nfev == callback_new_syntax.n_calls, \
|
||
|
"Callback is not called exactly once for every function eval."
|
||
|
|
||
|
def test_minimize_bounds(self):
|
||
|
def fun_check_bounds(x):
|
||
|
assert np.all(bounds.lb <= x) and np.all(x <= bounds.ub)
|
||
|
return self.fun(x)
|
||
|
|
||
|
# Case where the bounds are not active at the solution.
|
||
|
bounds = Bounds([4.5, 0.6], [5.0, 0.7])
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
sol = minimize(
|
||
|
fun_check_bounds,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
bounds=bounds,
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
solution = [np.sqrt(25.0 - 4.0 / 9.0), 2.0 / 3.0]
|
||
|
assert_allclose(sol.x, solution, atol=1e-4)
|
||
|
assert sol.success, sol.message
|
||
|
assert sol.maxcv < 1e-8, sol
|
||
|
assert np.all(bounds.lb <= sol.x) and np.all(sol.x <= bounds.ub), sol
|
||
|
assert sol.nfev <= 100, sol
|
||
|
assert sol.fun < self.fun(solution) + 1e-3, sol
|
||
|
|
||
|
# Case where the bounds are active at the solution.
|
||
|
bounds = Bounds([5.0, 0.6], [5.5, 0.65])
|
||
|
sol = minimize(
|
||
|
fun_check_bounds,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
bounds=bounds,
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
assert not sol.success, sol.message
|
||
|
assert sol.maxcv > 0.35, sol
|
||
|
assert np.all(bounds.lb <= sol.x) and np.all(sol.x <= bounds.ub), sol
|
||
|
assert sol.nfev <= 100, sol
|
||
|
|
||
|
def test_minimize_linear_constraints(self):
|
||
|
constraints = LinearConstraint([1.0, 1.0], 1.0, 1.0)
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
solution = [(4 - np.sqrt(7)) / 3, (np.sqrt(7) - 1) / 3]
|
||
|
assert_allclose(sol.x, solution, atol=1e-4)
|
||
|
assert sol.success, sol.message
|
||
|
assert sol.maxcv < 1e-8, sol
|
||
|
assert sol.nfev <= 100, sol
|
||
|
assert sol.fun < self.fun(solution) + 1e-3, sol
|
||
|
|
||
|
def test_minimize_args(self):
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
args=(2.0,),
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
solution = [np.sqrt(25.0 - 4.0 / 36.0), 2.0 / 6.0]
|
||
|
assert_allclose(sol.x, solution, atol=1e-4)
|
||
|
assert sol.success, sol.message
|
||
|
assert sol.maxcv < 1e-8, sol
|
||
|
assert sol.nfev <= 100, sol
|
||
|
assert sol.fun < self.fun(solution, 2.0) + 1e-3, sol
|
||
|
|
||
|
def test_minimize_array(self):
|
||
|
def fun_array(x, dim):
|
||
|
f = np.array(self.fun(x))
|
||
|
return np.reshape(f, (1,) * dim)
|
||
|
|
||
|
# The argument fun can return an array with a single element.
|
||
|
bounds = Bounds([4.5, 0.6], [5.0, 0.7])
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
bounds=bounds,
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
for dim in [0, 1, 2]:
|
||
|
sol_array = minimize(
|
||
|
fun_array,
|
||
|
self.x0,
|
||
|
args=(dim,),
|
||
|
method='cobyqa',
|
||
|
bounds=bounds,
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
assert_equal(sol.x, sol_array.x)
|
||
|
assert sol_array.success, sol_array.message
|
||
|
assert sol.fun == sol_array.fun
|
||
|
assert sol.maxcv == sol_array.maxcv
|
||
|
assert sol.nfev == sol_array.nfev
|
||
|
assert sol.nit == sol_array.nit
|
||
|
|
||
|
# The argument fun cannot return an array with more than one element.
|
||
|
with pytest.raises(TypeError):
|
||
|
minimize(
|
||
|
lambda x: np.array([self.fun(x), self.fun(x)]),
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
bounds=bounds,
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
|
||
|
def test_minimize_maxfev(self):
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
options = {'maxfev': 2}
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
options=options,
|
||
|
)
|
||
|
assert not sol.success, sol.message
|
||
|
assert sol.nfev <= 2, sol
|
||
|
|
||
|
def test_minimize_maxiter(self):
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
options = {'maxiter': 2}
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
options=options,
|
||
|
)
|
||
|
assert not sol.success, sol.message
|
||
|
assert sol.nit <= 2, sol
|
||
|
|
||
|
def test_minimize_f_target(self):
|
||
|
constraints = NonlinearConstraint(self.con, 0.0, 0.0)
|
||
|
sol_ref = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
options=self.options,
|
||
|
)
|
||
|
options = dict(self.options)
|
||
|
options['f_target'] = sol_ref.fun
|
||
|
sol = minimize(
|
||
|
self.fun,
|
||
|
self.x0,
|
||
|
method='cobyqa',
|
||
|
constraints=constraints,
|
||
|
options=options,
|
||
|
)
|
||
|
assert sol.success, sol.message
|
||
|
assert sol.maxcv < 1e-8, sol
|
||
|
assert sol.nfev <= sol_ref.nfev, sol
|
||
|
assert sol.fun <= sol_ref.fun, sol
|