demo + utils venv

This commit is contained in:
d3m1g0d
2019-02-03 13:40:10 +01:00
parent 5fa112490b
commit cfa9c8ea23
5994 changed files with 1353819 additions and 0 deletions
@@ -0,0 +1,447 @@
"""
Unit tests for the basin hopping global minimization algorithm.
"""
from __future__ import division, print_function, absolute_import
import copy
from numpy.testing import assert_almost_equal, assert_equal, assert_
from pytest import raises as assert_raises
import numpy as np
from numpy import cos, sin
from scipy.optimize import basinhopping, OptimizeResult
from scipy.optimize._basinhopping import (
Storage, RandomDisplacement, Metropolis, AdaptiveStepsize)
def func1d(x):
f = cos(14.5 * x - 0.3) + (x + 0.2) * x
df = np.array(-14.5 * sin(14.5 * x - 0.3) + 2. * x + 0.2)
return f, df
def func2d_nograd(x):
f = cos(14.5 * x[0] - 0.3) + (x[1] + 0.2) * x[1] + (x[0] + 0.2) * x[0]
return f
def func2d(x):
f = cos(14.5 * x[0] - 0.3) + (x[1] + 0.2) * x[1] + (x[0] + 0.2) * x[0]
df = np.zeros(2)
df[0] = -14.5 * sin(14.5 * x[0] - 0.3) + 2. * x[0] + 0.2
df[1] = 2. * x[1] + 0.2
return f, df
def func2d_easyderiv(x):
f = 2.0*x[0]**2 + 2.0*x[0]*x[1] + 2.0*x[1]**2 - 6.0*x[0]
df = np.zeros(2)
df[0] = 4.0*x[0] + 2.0*x[1] - 6.0
df[1] = 2.0*x[0] + 4.0*x[1]
return f, df
class MyTakeStep1(RandomDisplacement):
"""use a copy of displace, but have it set a special parameter to
make sure it's actually being used."""
def __init__(self):
self.been_called = False
super(MyTakeStep1, self).__init__()
def __call__(self, x):
self.been_called = True
return super(MyTakeStep1, self).__call__(x)
def myTakeStep2(x):
"""redo RandomDisplacement in function form without the attribute stepsize
to make sure everything still works ok
"""
s = 0.5
x += np.random.uniform(-s, s, np.shape(x))
return x
class MyAcceptTest(object):
"""pass a custom accept test
This does nothing but make sure it's being used and ensure all the
possible return values are accepted
"""
def __init__(self):
self.been_called = False
self.ncalls = 0
self.testres = [False, 'force accept', True, np.bool_(True),
np.bool_(False), [], {}, 0, 1]
def __call__(self, **kwargs):
self.been_called = True
self.ncalls += 1
if self.ncalls - 1 < len(self.testres):
return self.testres[self.ncalls - 1]
else:
return True
class MyCallBack(object):
"""pass a custom callback function
This makes sure it's being used. It also returns True after 10
steps to ensure that it's stopping early.
"""
def __init__(self):
self.been_called = False
self.ncalls = 0
def __call__(self, x, f, accepted):
self.been_called = True
self.ncalls += 1
if self.ncalls == 10:
return True
class TestBasinHopping(object):
def setup_method(self):
""" Tests setup.
Run tests based on the 1-D and 2-D functions described above.
"""
self.x0 = (1.0, [1.0, 1.0])
self.sol = (-0.195, np.array([-0.195, -0.1]))
self.tol = 3 # number of decimal places
self.niter = 100
self.disp = False
# fix random seed
np.random.seed(1234)
self.kwargs = {"method": "L-BFGS-B", "jac": True}
self.kwargs_nograd = {"method": "L-BFGS-B"}
def test_TypeError(self):
# test the TypeErrors are raised on bad input
i = 1
# if take_step is passed, it must be callable
assert_raises(TypeError, basinhopping, func2d, self.x0[i],
take_step=1)
# if accept_test is passed, it must be callable
assert_raises(TypeError, basinhopping, func2d, self.x0[i],
accept_test=1)
def test_1d_grad(self):
# test 1d minimizations with gradient
i = 0
res = basinhopping(func1d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=self.niter, disp=self.disp)
assert_almost_equal(res.x, self.sol[i], self.tol)
def test_2d(self):
# test 2d minimizations with gradient
i = 1
res = basinhopping(func2d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=self.niter, disp=self.disp)
assert_almost_equal(res.x, self.sol[i], self.tol)
assert_(res.nfev > 0)
def test_njev(self):
# test njev is returned correctly
i = 1
minimizer_kwargs = self.kwargs.copy()
# L-BFGS-B doesn't use njev, but BFGS does
minimizer_kwargs["method"] = "BFGS"
res = basinhopping(func2d, self.x0[i],
minimizer_kwargs=minimizer_kwargs, niter=self.niter,
disp=self.disp)
assert_(res.nfev > 0)
assert_equal(res.nfev, res.njev)
def test_jac(self):
# test jacobian returned
minimizer_kwargs = self.kwargs.copy()
# BFGS returns a Jacobian
minimizer_kwargs["method"] = "BFGS"
res = basinhopping(func2d_easyderiv, [0.0, 0.0],
minimizer_kwargs=minimizer_kwargs, niter=self.niter,
disp=self.disp)
assert_(hasattr(res.lowest_optimization_result, "jac"))
# in this case, the jacobian is just [df/dx, df/dy]
_, jacobian = func2d_easyderiv(res.x)
assert_almost_equal(res.lowest_optimization_result.jac, jacobian,
self.tol)
def test_2d_nograd(self):
# test 2d minimizations without gradient
i = 1
res = basinhopping(func2d_nograd, self.x0[i],
minimizer_kwargs=self.kwargs_nograd,
niter=self.niter, disp=self.disp)
assert_almost_equal(res.x, self.sol[i], self.tol)
def test_all_minimizers(self):
# test 2d minimizations with gradient. Nelder-Mead, Powell and COBYLA
# don't accept jac=True, so aren't included here.
i = 1
methods = ['CG', 'BFGS', 'Newton-CG', 'L-BFGS-B', 'TNC', 'SLSQP']
minimizer_kwargs = copy.copy(self.kwargs)
for method in methods:
minimizer_kwargs["method"] = method
res = basinhopping(func2d, self.x0[i],
minimizer_kwargs=minimizer_kwargs,
niter=self.niter, disp=self.disp)
assert_almost_equal(res.x, self.sol[i], self.tol)
def test_all_nograd_minimizers(self):
# test 2d minimizations without gradient. Newton-CG requires jac=True,
# so not included here.
i = 1
methods = ['CG', 'BFGS', 'L-BFGS-B', 'TNC', 'SLSQP',
'Nelder-Mead', 'Powell', 'COBYLA']
minimizer_kwargs = copy.copy(self.kwargs_nograd)
for method in methods:
minimizer_kwargs["method"] = method
res = basinhopping(func2d_nograd, self.x0[i],
minimizer_kwargs=minimizer_kwargs,
niter=self.niter, disp=self.disp)
tol = self.tol
if method == 'COBYLA':
tol = 2
assert_almost_equal(res.x, self.sol[i], decimal=tol)
def test_pass_takestep(self):
# test that passing a custom takestep works
# also test that the stepsize is being adjusted
takestep = MyTakeStep1()
initial_step_size = takestep.stepsize
i = 1
res = basinhopping(func2d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=self.niter, disp=self.disp,
take_step=takestep)
assert_almost_equal(res.x, self.sol[i], self.tol)
assert_(takestep.been_called)
# make sure that the built in adaptive step size has been used
assert_(initial_step_size != takestep.stepsize)
def test_pass_simple_takestep(self):
# test that passing a custom takestep without attribute stepsize
takestep = myTakeStep2
i = 1
res = basinhopping(func2d_nograd, self.x0[i],
minimizer_kwargs=self.kwargs_nograd,
niter=self.niter, disp=self.disp,
take_step=takestep)
assert_almost_equal(res.x, self.sol[i], self.tol)
def test_pass_accept_test(self):
# test passing a custom accept test
# makes sure it's being used and ensures all the possible return values
# are accepted.
accept_test = MyAcceptTest()
i = 1
# there's no point in running it more than a few steps.
basinhopping(func2d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=10, disp=self.disp, accept_test=accept_test)
assert_(accept_test.been_called)
def test_pass_callback(self):
# test passing a custom callback function
# This makes sure it's being used. It also returns True after 10 steps
# to ensure that it's stopping early.
callback = MyCallBack()
i = 1
# there's no point in running it more than a few steps.
res = basinhopping(func2d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=30, disp=self.disp, callback=callback)
assert_(callback.been_called)
assert_("callback" in res.message[0])
assert_equal(res.nit, 10)
def test_minimizer_fail(self):
# test if a minimizer fails
i = 1
self.kwargs["options"] = dict(maxiter=0)
self.niter = 10
res = basinhopping(func2d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=self.niter, disp=self.disp)
# the number of failed minimizations should be the number of
# iterations + 1
assert_equal(res.nit + 1, res.minimization_failures)
def test_niter_zero(self):
# gh5915, what happens if you call basinhopping with niter=0
i = 0
basinhopping(func1d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=0, disp=self.disp)
def test_seed_reproducibility(self):
# seed should ensure reproducibility between runs
minimizer_kwargs = {"method": "L-BFGS-B", "jac": True}
f_1 = []
def callback(x, f, accepted):
f_1.append(f)
basinhopping(func2d, [1.0, 1.0], minimizer_kwargs=minimizer_kwargs,
niter=10, callback=callback, seed=10)
f_2 = []
def callback2(x, f, accepted):
f_2.append(f)
basinhopping(func2d, [1.0, 1.0], minimizer_kwargs=minimizer_kwargs,
niter=10, callback=callback2, seed=10)
assert_equal(np.array(f_1), np.array(f_2))
def test_monotonic_basin_hopping(self):
# test 1d minimizations with gradient and T=0
i = 0
res = basinhopping(func1d, self.x0[i], minimizer_kwargs=self.kwargs,
niter=self.niter, disp=self.disp, T=0)
assert_almost_equal(res.x, self.sol[i], self.tol)
class Test_Storage(object):
def setup_method(self):
self.x0 = np.array(1)
self.f0 = 0
minres = OptimizeResult()
minres.x = self.x0
minres.fun = self.f0
self.storage = Storage(minres)
def test_higher_f_rejected(self):
new_minres = OptimizeResult()
new_minres.x = self.x0 + 1
new_minres.fun = self.f0 + 1
ret = self.storage.update(new_minres)
minres = self.storage.get_lowest()
assert_equal(self.x0, minres.x)
assert_equal(self.f0, minres.fun)
assert_(not ret)
def test_lower_f_accepted(self):
new_minres = OptimizeResult()
new_minres.x = self.x0 + 1
new_minres.fun = self.f0 - 1
ret = self.storage.update(new_minres)
minres = self.storage.get_lowest()
assert_(self.x0 != minres.x)
assert_(self.f0 != minres.fun)
assert_(ret)
class Test_RandomDisplacement(object):
def setup_method(self):
self.stepsize = 1.0
self.displace = RandomDisplacement(stepsize=self.stepsize)
self.N = 300000
self.x0 = np.zeros([self.N])
def test_random(self):
# the mean should be 0
# the variance should be (2*stepsize)**2 / 12
# note these tests are random, they will fail from time to time
x = self.displace(self.x0)
v = (2. * self.stepsize) ** 2 / 12
assert_almost_equal(np.mean(x), 0., 1)
assert_almost_equal(np.var(x), v, 1)
class Test_Metropolis(object):
def setup_method(self):
self.T = 2.
self.met = Metropolis(self.T)
def test_boolean_return(self):
# the return must be a bool. else an error will be raised in
# basinhopping
ret = self.met(f_new=0., f_old=1.)
assert isinstance(ret, bool)
def test_lower_f_accepted(self):
assert_(self.met(f_new=0., f_old=1.))
def test_KeyError(self):
# should raise KeyError if kwargs f_old or f_new is not passed
assert_raises(KeyError, self.met, f_old=1.)
assert_raises(KeyError, self.met, f_new=1.)
def test_accept(self):
# test that steps are randomly accepted for f_new > f_old
one_accept = False
one_reject = False
for i in range(1000):
if one_accept and one_reject:
break
ret = self.met(f_new=1., f_old=0.5)
if ret:
one_accept = True
else:
one_reject = True
assert_(one_accept)
assert_(one_reject)
def test_GH7495(self):
# an overflow in exp was producing a RuntimeWarning
# create own object here in case someone changes self.T
met = Metropolis(2)
with np.errstate(over='raise'):
met.accept_reject(0, 2000)
class Test_AdaptiveStepsize(object):
def setup_method(self):
self.stepsize = 1.
self.ts = RandomDisplacement(stepsize=self.stepsize)
self.target_accept_rate = 0.5
self.takestep = AdaptiveStepsize(takestep=self.ts, verbose=False,
accept_rate=self.target_accept_rate)
def test_adaptive_increase(self):
# if few steps are rejected, the stepsize should increase
x = 0.
self.takestep(x)
self.takestep.report(False)
for i in range(self.takestep.interval):
self.takestep(x)
self.takestep.report(True)
assert_(self.ts.stepsize > self.stepsize)
def test_adaptive_decrease(self):
# if few steps are rejected, the stepsize should increase
x = 0.
self.takestep(x)
self.takestep.report(True)
for i in range(self.takestep.interval):
self.takestep(x)
self.takestep.report(False)
assert_(self.ts.stepsize < self.stepsize)
def test_all_accepted(self):
# test that everything works OK if all steps were accepted
x = 0.
for i in range(self.takestep.interval + 1):
self.takestep(x)
self.takestep.report(True)
assert_(self.ts.stepsize > self.stepsize)
def test_all_rejected(self):
# test that everything works OK if all steps were rejected
x = 0.
for i in range(self.takestep.interval + 1):
self.takestep(x)
self.takestep.report(False)
assert_(self.ts.stepsize < self.stepsize)
@@ -0,0 +1,540 @@
"""
Unit tests for the differential global minimization algorithm.
"""
from scipy.optimize import _differentialevolution
from scipy.optimize._differentialevolution import DifferentialEvolutionSolver
from scipy.optimize import differential_evolution
import numpy as np
from scipy.optimize import rosen
from numpy.testing import (assert_equal, assert_allclose,
assert_almost_equal,
assert_string_equal, assert_)
from pytest import raises as assert_raises, warns
class TestDifferentialEvolutionSolver(object):
def setup_method(self):
self.old_seterr = np.seterr(invalid='raise')
self.limits = np.array([[0., 0.],
[2., 2.]])
self.bounds = [(0., 2.), (0., 2.)]
self.dummy_solver = DifferentialEvolutionSolver(self.quadratic,
[(0, 100)])
# dummy_solver2 will be used to test mutation strategies
self.dummy_solver2 = DifferentialEvolutionSolver(self.quadratic,
[(0, 1)],
popsize=7,
mutation=0.5)
# create a population that's only 7 members long
# [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
population = np.atleast_2d(np.arange(0.1, 0.8, 0.1)).T
self.dummy_solver2.population = population
def teardown_method(self):
np.seterr(**self.old_seterr)
def quadratic(self, x):
return x[0]**2
def test__strategy_resolves(self):
# test that the correct mutation function is resolved by
# different requested strategy arguments
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='best1exp')
assert_equal(solver.strategy, 'best1exp')
assert_equal(solver.mutation_func.__name__, '_best1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='best1bin')
assert_equal(solver.strategy, 'best1bin')
assert_equal(solver.mutation_func.__name__, '_best1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='rand1bin')
assert_equal(solver.strategy, 'rand1bin')
assert_equal(solver.mutation_func.__name__, '_rand1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='rand1exp')
assert_equal(solver.strategy, 'rand1exp')
assert_equal(solver.mutation_func.__name__, '_rand1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='rand2exp')
assert_equal(solver.strategy, 'rand2exp')
assert_equal(solver.mutation_func.__name__, '_rand2')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='best2bin')
assert_equal(solver.strategy, 'best2bin')
assert_equal(solver.mutation_func.__name__, '_best2')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='rand2bin')
assert_equal(solver.strategy, 'rand2bin')
assert_equal(solver.mutation_func.__name__, '_rand2')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='rand2exp')
assert_equal(solver.strategy, 'rand2exp')
assert_equal(solver.mutation_func.__name__, '_rand2')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='randtobest1bin')
assert_equal(solver.strategy, 'randtobest1bin')
assert_equal(solver.mutation_func.__name__, '_randtobest1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='randtobest1exp')
assert_equal(solver.strategy, 'randtobest1exp')
assert_equal(solver.mutation_func.__name__, '_randtobest1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='currenttobest1bin')
assert_equal(solver.strategy, 'currenttobest1bin')
assert_equal(solver.mutation_func.__name__, '_currenttobest1')
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='currenttobest1exp')
assert_equal(solver.strategy, 'currenttobest1exp')
assert_equal(solver.mutation_func.__name__, '_currenttobest1')
def test__mutate1(self):
# strategies */1/*, i.e. rand/1/bin, best/1/exp, etc.
result = np.array([0.05])
trial = self.dummy_solver2._best1((2, 3, 4, 5, 6))
assert_allclose(trial, result)
result = np.array([0.25])
trial = self.dummy_solver2._rand1((2, 3, 4, 5, 6))
assert_allclose(trial, result)
def test__mutate2(self):
# strategies */2/*, i.e. rand/2/bin, best/2/exp, etc.
# [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
result = np.array([-0.1])
trial = self.dummy_solver2._best2((2, 3, 4, 5, 6))
assert_allclose(trial, result)
result = np.array([0.1])
trial = self.dummy_solver2._rand2((2, 3, 4, 5, 6))
assert_allclose(trial, result)
def test__randtobest1(self):
# strategies randtobest/1/*
result = np.array([0.15])
trial = self.dummy_solver2._randtobest1((2, 3, 4, 5, 6))
assert_allclose(trial, result)
def test__currenttobest1(self):
# strategies currenttobest/1/*
result = np.array([0.1])
trial = self.dummy_solver2._currenttobest1(1, (2, 3, 4, 5, 6))
assert_allclose(trial, result)
def test_can_init_with_dithering(self):
mutation = (0.5, 1)
solver = DifferentialEvolutionSolver(self.quadratic,
self.bounds,
mutation=mutation)
assert_equal(solver.dither, list(mutation))
def test_invalid_mutation_values_arent_accepted(self):
func = rosen
mutation = (0.5, 3)
assert_raises(ValueError,
DifferentialEvolutionSolver,
func,
self.bounds,
mutation=mutation)
mutation = (-1, 1)
assert_raises(ValueError,
DifferentialEvolutionSolver,
func,
self.bounds,
mutation=mutation)
mutation = (0.1, np.nan)
assert_raises(ValueError,
DifferentialEvolutionSolver,
func,
self.bounds,
mutation=mutation)
mutation = 0.5
solver = DifferentialEvolutionSolver(func,
self.bounds,
mutation=mutation)
assert_equal(0.5, solver.scale)
assert_equal(None, solver.dither)
def test__scale_parameters(self):
trial = np.array([0.3])
assert_equal(30, self.dummy_solver._scale_parameters(trial))
# it should also work with the limits reversed
self.dummy_solver.limits = np.array([[100], [0.]])
assert_equal(30, self.dummy_solver._scale_parameters(trial))
def test__unscale_parameters(self):
trial = np.array([30])
assert_equal(0.3, self.dummy_solver._unscale_parameters(trial))
# it should also work with the limits reversed
self.dummy_solver.limits = np.array([[100], [0.]])
assert_equal(0.3, self.dummy_solver._unscale_parameters(trial))
def test__ensure_constraint(self):
trial = np.array([1.1, -100, 0.9, 2., 300., -0.00001])
self.dummy_solver._ensure_constraint(trial)
assert_equal(trial[2], 0.9)
assert_(np.logical_and(trial >= 0, trial <= 1).all())
def test_differential_evolution(self):
# test that the Jmin of DifferentialEvolutionSolver
# is the same as the function evaluation
solver = DifferentialEvolutionSolver(self.quadratic, [(-2, 2)])
result = solver.solve()
assert_almost_equal(result.fun, self.quadratic(result.x))
def test_best_solution_retrieval(self):
# test that the getter property method for the best solution works.
solver = DifferentialEvolutionSolver(self.quadratic, [(-2, 2)])
result = solver.solve()
assert_almost_equal(result.x, solver.x)
def test_callback_terminates(self):
# test that if the callback returns true, then the minimization halts
bounds = [(0, 2), (0, 2)]
def callback(param, convergence=0.):
return True
result = differential_evolution(rosen, bounds, callback=callback)
assert_string_equal(result.message,
'callback function requested stop early '
'by returning True')
def test_args_tuple_is_passed(self):
# test that the args tuple is passed to the cost function properly.
bounds = [(-10, 10)]
args = (1., 2., 3.)
def quadratic(x, *args):
if type(args) != tuple:
raise ValueError('args should be a tuple')
return args[0] + args[1] * x + args[2] * x**2.
result = differential_evolution(quadratic,
bounds,
args=args,
polish=True)
assert_almost_equal(result.fun, 2 / 3.)
def test_init_with_invalid_strategy(self):
# test that passing an invalid strategy raises ValueError
func = rosen
bounds = [(-3, 3)]
assert_raises(ValueError,
differential_evolution,
func,
bounds,
strategy='abc')
def test_bounds_checking(self):
# test that the bounds checking works
func = rosen
bounds = [(-3, None)]
assert_raises(ValueError,
differential_evolution,
func,
bounds)
bounds = [(-3)]
assert_raises(ValueError,
differential_evolution,
func,
bounds)
bounds = [(-3, 3), (3, 4, 5)]
assert_raises(ValueError,
differential_evolution,
func,
bounds)
def test_select_samples(self):
# select_samples should return 5 separate random numbers.
limits = np.arange(12., dtype='float64').reshape(2, 6)
bounds = list(zip(limits[0, :], limits[1, :]))
solver = DifferentialEvolutionSolver(None, bounds, popsize=1)
candidate = 0
r1, r2, r3, r4, r5 = solver._select_samples(candidate, 5)
assert_equal(
len(np.unique(np.array([candidate, r1, r2, r3, r4, r5]))), 6)
def test_maxiter_stops_solve(self):
# test that if the maximum number of iterations is exceeded
# the solver stops.
solver = DifferentialEvolutionSolver(rosen, self.bounds, maxiter=1)
result = solver.solve()
assert_equal(result.success, False)
assert_equal(result.message,
'Maximum number of iterations has been exceeded.')
def test_maxfun_stops_solve(self):
# test that if the maximum number of function evaluations is exceeded
# during initialisation the solver stops
solver = DifferentialEvolutionSolver(rosen, self.bounds, maxfun=1,
polish=False)
result = solver.solve()
assert_equal(result.nfev, 2)
assert_equal(result.success, False)
assert_equal(result.message,
'Maximum number of function evaluations has '
'been exceeded.')
# test that if the maximum number of function evaluations is exceeded
# during the actual minimisation, then the solver stops.
# Have to turn polishing off, as this will still occur even if maxfun
# is reached. For popsize=5 and len(bounds)=2, then there are only 10
# function evaluations during initialisation.
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
popsize=5,
polish=False,
maxfun=40)
result = solver.solve()
assert_equal(result.nfev, 41)
assert_equal(result.success, False)
assert_equal(result.message,
'Maximum number of function evaluations has '
'been exceeded.')
# now repeat for updating='deferred version
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
popsize=5,
polish=False,
maxfun=40,
updating='deferred')
result = solver.solve()
assert_equal(result.nfev, 40)
assert_equal(result.success, False)
assert_equal(result.message,
'Maximum number of function evaluations has '
'been reached.')
def test_quadratic(self):
# test the quadratic function from object
solver = DifferentialEvolutionSolver(self.quadratic,
[(-100, 100)],
tol=0.02)
solver.solve()
assert_equal(np.argmin(solver.population_energies), 0)
def test_quadratic_from_diff_ev(self):
# test the quadratic function from differential_evolution function
differential_evolution(self.quadratic,
[(-100, 100)],
tol=0.02)
def test_seed_gives_repeatability(self):
result = differential_evolution(self.quadratic,
[(-100, 100)],
polish=False,
seed=1,
tol=0.5)
result2 = differential_evolution(self.quadratic,
[(-100, 100)],
polish=False,
seed=1,
tol=0.5)
assert_equal(result.x, result2.x)
assert_equal(result.nfev, result2.nfev)
def test_exp_runs(self):
# test whether exponential mutation loop runs
solver = DifferentialEvolutionSolver(rosen,
self.bounds,
strategy='best1exp',
maxiter=1)
solver.solve()
def test_gh_4511_regression(self):
# This modification of the differential evolution docstring example
# uses a custom popsize that had triggered an off-by-one error.
# Because we do not care about solving the optimization problem in
# this test, we use maxiter=1 to reduce the testing time.
bounds = [(-5, 5), (-5, 5)]
result = differential_evolution(rosen, bounds, popsize=1815, maxiter=1)
def test_calculate_population_energies(self):
# if popsize is 3 then the overall generation has size (6,)
solver = DifferentialEvolutionSolver(rosen, self.bounds, popsize=3)
solver._calculate_population_energies(solver.population)
solver._promote_lowest_energy()
assert_equal(np.argmin(solver.population_energies), 0)
# initial calculation of the energies should require 6 nfev.
assert_equal(solver._nfev, 6)
def test_iteration(self):
# test that DifferentialEvolutionSolver is iterable
# if popsize is 3 then the overall generation has size (6,)
solver = DifferentialEvolutionSolver(rosen, self.bounds, popsize=3,
maxfun=12)
x, fun = next(solver)
assert_equal(np.size(x, 0), 2)
# 6 nfev are required for initial calculation of energies, 6 nfev are
# required for the evolution of the 6 population members.
assert_equal(solver._nfev, 12)
# the next generation should halt because it exceeds maxfun
assert_raises(StopIteration, next, solver)
# check a proper minimisation can be done by an iterable solver
solver = DifferentialEvolutionSolver(rosen, self.bounds)
for i, soln in enumerate(solver):
x_current, fun_current = soln
# need to have this otherwise the solver would never stop.
if i == 1000:
break
assert_almost_equal(fun_current, 0)
def test_convergence(self):
solver = DifferentialEvolutionSolver(rosen, self.bounds, tol=0.2,
polish=False)
solver.solve()
assert_(solver.convergence < 0.2)
def test_maxiter_none_GH5731(self):
# Pre 0.17 the previous default for maxiter and maxfun was None.
# the numerical defaults are now 1000 and np.inf. However, some scripts
# will still supply None for both of those, this will raise a TypeError
# in the solve method.
solver = DifferentialEvolutionSolver(rosen, self.bounds, maxiter=None,
maxfun=None)
solver.solve()
def test_population_initiation(self):
# test the different modes of population initiation
# init must be either 'latinhypercube' or 'random'
# raising ValueError is something else is passed in
assert_raises(ValueError,
DifferentialEvolutionSolver,
*(rosen, self.bounds),
**{'init': 'rubbish'})
solver = DifferentialEvolutionSolver(rosen, self.bounds)
# check that population initiation:
# 1) resets _nfev to 0
# 2) all population energies are np.inf
solver.init_population_random()
assert_equal(solver._nfev, 0)
assert_(np.all(np.isinf(solver.population_energies)))
solver.init_population_lhs()
assert_equal(solver._nfev, 0)
assert_(np.all(np.isinf(solver.population_energies)))
# we should be able to initialise with our own array
population = np.linspace(-1, 3, 10).reshape(5, 2)
solver = DifferentialEvolutionSolver(rosen, self.bounds,
init=population,
strategy='best2bin',
atol=0.01, seed=1, popsize=5)
assert_equal(solver._nfev, 0)
assert_(np.all(np.isinf(solver.population_energies)))
assert_(solver.num_population_members == 5)
assert_(solver.population_shape == (5, 2))
# check that the population was initialised correctly
unscaled_population = np.clip(solver._unscale_parameters(population),
0, 1)
assert_almost_equal(solver.population[:5], unscaled_population)
# population values need to be clipped to bounds
assert_almost_equal(np.min(solver.population[:5]), 0)
assert_almost_equal(np.max(solver.population[:5]), 1)
# shouldn't be able to initialise with an array if it's the wrong shape
# this would have too many parameters
population = np.linspace(-1, 3, 15).reshape(5, 3)
assert_raises(ValueError,
DifferentialEvolutionSolver,
*(rosen, self.bounds),
**{'init': population})
def test_infinite_objective_function(self):
# Test that there are no problems if the objective function
# returns inf on some runs
def sometimes_inf(x):
if x[0] < .5:
return np.inf
return x[1]
bounds = [(0, 1), (0, 1)]
x_fit = differential_evolution(sometimes_inf,
bounds=[(0, 1), (0, 1)],
disp=False)
def test_deferred_updating(self):
# check setting of deferred updating, with default workers
bounds = [(0., 2.), (0., 2.), (0, 2), (0, 2)]
solver = DifferentialEvolutionSolver(rosen, bounds, updating='deferred')
assert_(solver._updating == 'deferred')
assert_(solver._mapwrapper._mapfunc is map)
solver.solve()
def test_immediate_updating(self):
# check setting of immediate updating, with default workers
bounds = [(0., 2.), (0., 2.)]
solver = DifferentialEvolutionSolver(rosen, bounds)
assert_(solver._updating == 'immediate')
# should raise a UserWarning because the updating='immediate'
# is being overriden by the workers keyword
with warns(UserWarning):
solver = DifferentialEvolutionSolver(rosen, bounds, workers=2)
assert_(solver._updating == 'deferred')
def test_parallel(self):
# smoke test for parallelisation with deferred updating
bounds = [(0., 2.), (0., 2.)]
with DifferentialEvolutionSolver(rosen, bounds,
updating='deferred',
workers=2) as solver:
assert_(solver._mapwrapper.pool is not None)
assert_(solver._updating == 'deferred')
solver.solve()
def test_converged(self):
solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)])
solver.solve()
assert_(solver.converged())
@@ -0,0 +1,266 @@
# 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
@@ -0,0 +1,365 @@
"""
Unit test for Linear Programming via Simplex Algorithm.
"""
from __future__ import division, print_function, absolute_import
import numpy as np
from numpy.testing import assert_, assert_allclose
from pytest import raises as assert_raises
from scipy.optimize._linprog_util import _clean_inputs
from copy import deepcopy
def test_aliasing():
c = 1
A_ub = [[1]]
b_ub = [1]
A_eq = [[1]]
b_eq = [1]
bounds = (-np.inf, np.inf)
c_copy = deepcopy(c)
A_ub_copy = deepcopy(A_ub)
b_ub_copy = deepcopy(b_ub)
A_eq_copy = deepcopy(A_eq)
b_eq_copy = deepcopy(b_eq)
bounds_copy = deepcopy(bounds)
_clean_inputs(c, A_ub, b_ub, A_eq, b_eq, bounds)
assert_(c == c_copy, "c modified by _clean_inputs")
assert_(A_ub == A_ub_copy, "A_ub modified by _clean_inputs")
assert_(b_ub == b_ub_copy, "b_ub modified by _clean_inputs")
assert_(A_eq == A_eq_copy, "A_eq modified by _clean_inputs")
assert_(b_eq == b_eq_copy, "b_eq modified by _clean_inputs")
assert_(bounds == bounds_copy, "bounds modified by _clean_inputs")
def test_aliasing2():
c = np.array([1, 1])
A_ub = np.array([[1, 1], [2, 2]])
b_ub = np.array([[1], [1]])
A_eq = np.array([[1, 1]])
b_eq = np.array([1])
bounds = [(-np.inf, np.inf), (None, 1)]
c_copy = c.copy()
A_ub_copy = A_ub.copy()
b_ub_copy = b_ub.copy()
A_eq_copy = A_eq.copy()
b_eq_copy = b_eq.copy()
bounds_copy = deepcopy(bounds)
_clean_inputs(c, A_ub, b_ub, A_eq, b_eq, bounds)
assert_allclose(c, c_copy, err_msg="c modified by _clean_inputs")
assert_allclose(A_ub, A_ub_copy, err_msg="A_ub modified by _clean_inputs")
assert_allclose(b_ub, b_ub_copy, err_msg="b_ub modified by _clean_inputs")
assert_allclose(A_eq, A_eq_copy, err_msg="A_eq modified by _clean_inputs")
assert_allclose(b_eq, b_eq_copy, err_msg="b_eq modified by _clean_inputs")
assert_(bounds == bounds_copy, "bounds modified by _clean_inputs")
def test_missing_inputs():
c = [1, 2]
A_ub = np.array([[1, 1], [2, 2]])
b_ub = np.array([1, 1])
A_eq = np.array([[1, 1], [2, 2]])
b_eq = np.array([1, 1])
assert_raises(TypeError, _clean_inputs)
assert_raises(TypeError, _clean_inputs, c=None)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=A_ub)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=A_ub, b_ub=None)
assert_raises(ValueError, _clean_inputs, c=c, b_ub=b_ub)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=None, b_ub=b_ub)
assert_raises(ValueError, _clean_inputs, c=c, A_eq=A_eq)
assert_raises(ValueError, _clean_inputs, c=c, A_eq=A_eq, b_eq=None)
assert_raises(ValueError, _clean_inputs, c=c, b_eq=b_eq)
assert_raises(ValueError, _clean_inputs, c=c, A_eq=None, b_eq=b_eq)
def test_too_many_dimensions():
cb = [1, 2, 3, 4]
A = np.random.rand(4, 4)
bad2D = [[1, 2], [3, 4]]
bad3D = np.random.rand(4, 4, 4)
assert_raises(ValueError, _clean_inputs, c=bad2D, A_ub=A, b_ub=cb)
assert_raises(ValueError, _clean_inputs, c=cb, A_ub=bad3D, b_ub=cb)
assert_raises(ValueError, _clean_inputs, c=cb, A_ub=A, b_ub=bad2D)
assert_raises(ValueError, _clean_inputs, c=cb, A_eq=bad3D, b_eq=cb)
assert_raises(ValueError, _clean_inputs, c=cb, A_eq=A, b_eq=bad2D)
def test_too_few_dimensions():
bad = np.random.rand(4, 4).ravel()
cb = np.random.rand(4)
assert_raises(ValueError, _clean_inputs, c=cb, A_ub=bad, b_ub=cb)
assert_raises(ValueError, _clean_inputs, c=cb, A_eq=bad, b_eq=cb)
def test_inconsistent_dimensions():
m = 2
n = 4
c = [1, 2, 3, 4]
Agood = np.random.rand(m, n)
Abad = np.random.rand(m, n + 1)
bgood = np.random.rand(m)
bbad = np.random.rand(m + 1)
boundsbad = [(0, 1)] * (n + 1)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=Abad, b_ub=bgood)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=Agood, b_ub=bbad)
assert_raises(ValueError, _clean_inputs, c=c, A_eq=Abad, b_eq=bgood)
assert_raises(ValueError, _clean_inputs, c=c, A_eq=Agood, b_eq=bbad)
assert_raises(ValueError, _clean_inputs, c=c, bounds=boundsbad)
def test_type_errors():
bad = "hello"
c = [1, 2]
A_ub = np.array([[1, 1], [2, 2]])
b_ub = np.array([1, 1])
A_eq = np.array([[1, 1], [2, 2]])
b_eq = np.array([1, 1])
bounds = [(0, 1)]
assert_raises(
TypeError,
_clean_inputs,
c=bad,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=bad,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=bad,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=bad,
b_eq=b_eq,
bounds=bounds)
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bad)
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds="hi")
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=["hi"])
assert_raises(
TypeError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=[
("hi")])
assert_raises(TypeError, _clean_inputs, c=c, A_ub=A_ub,
b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=[(1, "")])
assert_raises(TypeError, _clean_inputs, c=c, A_ub=A_ub,
b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=[(1, 2), (1, "")])
def test_non_finite_errors():
c = [1, 2]
A_ub = np.array([[1, 1], [2, 2]])
b_ub = np.array([1, 1])
A_eq = np.array([[1, 1], [2, 2]])
b_eq = np.array([1, 1])
bounds = [(0, 1)]
assert_raises(
ValueError, _clean_inputs, c=[0, None], A_ub=A_ub, b_ub=b_ub,
A_eq=A_eq, b_eq=b_eq, bounds=bounds)
assert_raises(
ValueError, _clean_inputs, c=[np.inf, 0], A_ub=A_ub, b_ub=b_ub,
A_eq=A_eq, b_eq=b_eq, bounds=bounds)
assert_raises(
ValueError, _clean_inputs, c=[0, -np.inf], A_ub=A_ub, b_ub=b_ub,
A_eq=A_eq, b_eq=b_eq, bounds=bounds)
assert_raises(
ValueError, _clean_inputs, c=[np.nan, 0], A_ub=A_ub, b_ub=b_ub,
A_eq=A_eq, b_eq=b_eq, bounds=bounds)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=[[1, 2], [None, 1]],
b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds)
assert_raises(
ValueError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=[
np.inf,
1],
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_raises(ValueError, _clean_inputs, c=c, A_ub=A_ub, b_ub=b_ub, A_eq=[
[1, 2], [1, -np.inf]], b_eq=b_eq, bounds=bounds)
assert_raises(
ValueError,
_clean_inputs,
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=[
1,
np.nan],
bounds=bounds)
def test__clean_inputs1():
c = [1, 2]
A_ub = [[1, 1], [2, 2]]
b_ub = [1, 1]
A_eq = [[1, 1], [2, 2]]
b_eq = [1, 1]
bounds = None
outputs = _clean_inputs(
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_allclose(outputs[0], np.array(c))
assert_allclose(outputs[1], np.array(A_ub))
assert_allclose(outputs[2], np.array(b_ub))
assert_allclose(outputs[3], np.array(A_eq))
assert_allclose(outputs[4], np.array(b_eq))
assert_(outputs[5] == [(0, None)] * 2, "")
assert_(outputs[0].shape == (2,), "")
assert_(outputs[1].shape == (2, 2), "")
assert_(outputs[2].shape == (2,), "")
assert_(outputs[3].shape == (2, 2), "")
assert_(outputs[4].shape == (2,), "")
def test__clean_inputs2():
c = 1
A_ub = [[1]]
b_ub = 1
A_eq = [[1]]
b_eq = 1
bounds = (0, 1)
outputs = _clean_inputs(
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_allclose(outputs[0], np.array(c))
assert_allclose(outputs[1], np.array(A_ub))
assert_allclose(outputs[2], np.array(b_ub))
assert_allclose(outputs[3], np.array(A_eq))
assert_allclose(outputs[4], np.array(b_eq))
assert_(outputs[5] == [(0, 1)], "")
assert_(outputs[0].shape == (1,), "")
assert_(outputs[1].shape == (1, 1), "")
assert_(outputs[2].shape == (1,), "")
assert_(outputs[3].shape == (1, 1), "")
assert_(outputs[4].shape == (1,), "")
def test__clean_inputs3():
c = [[1, 2]]
A_ub = np.random.rand(2, 2)
b_ub = [[1], [2]]
A_eq = np.random.rand(2, 2)
b_eq = [[1], [2]]
bounds = [(0, 1)]
outputs = _clean_inputs(
c=c,
A_ub=A_ub,
b_ub=b_ub,
A_eq=A_eq,
b_eq=b_eq,
bounds=bounds)
assert_allclose(outputs[0], np.array([1, 2]))
assert_allclose(outputs[2], np.array([1, 2]))
assert_allclose(outputs[4], np.array([1, 2]))
assert_(outputs[5] == [(0, 1)] * 2, "")
assert_(outputs[0].shape == (2,), "")
assert_(outputs[2].shape == (2,), "")
assert_(outputs[4].shape == (2,), "")
def test_bad_bounds():
c = [1, 2]
assert_raises(ValueError, _clean_inputs, c=c, bounds=(1, -2))
assert_raises(ValueError, _clean_inputs, c=c, bounds=[(1, -2)])
assert_raises(ValueError, _clean_inputs, c=c, bounds=[(1, -2), (1, 2)])
assert_raises(ValueError, _clean_inputs, c=c, bounds=(1, 2, 2))
assert_raises(ValueError, _clean_inputs, c=c, bounds=[(1, 2, 2)])
assert_raises(ValueError, _clean_inputs, c=c, bounds=[(1, 2), (1, 2, 2)])
assert_raises(ValueError, _clean_inputs, c=c,
bounds=[(1, 2), (1, 2), (1, 2)])
def test_good_bounds():
c = [1, 2]
outputs = _clean_inputs(c=c, bounds=None)
assert_(outputs[5] == [(0, None)] * 2, "")
outputs = _clean_inputs(c=c, bounds=(1, 2))
assert_(outputs[5] == [(1, 2)] * 2, "")
outputs = _clean_inputs(c=c, bounds=[(1, 2)])
assert_(outputs[5] == [(1, 2)] * 2, "")
outputs = _clean_inputs(c=c, bounds=[(1, np.inf)])
assert_(outputs[5] == [(1, None)] * 2, "")
outputs = _clean_inputs(c=c, bounds=[(-np.inf, 1)])
assert_(outputs[5] == [(None, 1)] * 2, "")
outputs = _clean_inputs(c=c, bounds=[(-np.inf, np.inf), (-np.inf, np.inf)])
assert_(outputs[5] == [(None, None)] * 2, "")
@@ -0,0 +1,598 @@
from __future__ import division
import math
from itertools import product
import numpy as np
from numpy.testing import assert_allclose, assert_equal, assert_
from pytest import raises as assert_raises
from scipy.sparse import csr_matrix, csc_matrix, lil_matrix
from scipy.optimize._numdiff import (
_adjust_scheme_to_bounds, approx_derivative, check_derivative,
group_columns)
def test_group_columns():
structure = [
[1, 1, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0],
[0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 0, 0, 0]
]
for transform in [np.asarray, csr_matrix, csc_matrix, lil_matrix]:
A = transform(structure)
order = np.arange(6)
groups_true = np.array([0, 1, 2, 0, 1, 2])
groups = group_columns(A, order)
assert_equal(groups, groups_true)
order = [1, 2, 4, 3, 5, 0]
groups_true = np.array([2, 0, 1, 2, 0, 1])
groups = group_columns(A, order)
assert_equal(groups, groups_true)
# Test repeatability.
groups_1 = group_columns(A)
groups_2 = group_columns(A)
assert_equal(groups_1, groups_2)
class TestAdjustSchemeToBounds(object):
def test_no_bounds(self):
x0 = np.zeros(3)
h = np.ones(3) * 1e-2
inf_lower = np.empty_like(x0)
inf_upper = np.empty_like(x0)
inf_lower.fill(-np.inf)
inf_upper.fill(np.inf)
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 1, '1-sided', inf_lower, inf_upper)
assert_allclose(h_adjusted, h)
assert_(np.all(one_sided))
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 2, '1-sided', inf_lower, inf_upper)
assert_allclose(h_adjusted, h)
assert_(np.all(one_sided))
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 1, '2-sided', inf_lower, inf_upper)
assert_allclose(h_adjusted, h)
assert_(np.all(~one_sided))
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 2, '2-sided', inf_lower, inf_upper)
assert_allclose(h_adjusted, h)
assert_(np.all(~one_sided))
def test_with_bound(self):
x0 = np.array([0.0, 0.85, -0.85])
lb = -np.ones(3)
ub = np.ones(3)
h = np.array([1, 1, -1]) * 1e-1
h_adjusted, _ = _adjust_scheme_to_bounds(x0, h, 1, '1-sided', lb, ub)
assert_allclose(h_adjusted, h)
h_adjusted, _ = _adjust_scheme_to_bounds(x0, h, 2, '1-sided', lb, ub)
assert_allclose(h_adjusted, np.array([1, -1, 1]) * 1e-1)
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 1, '2-sided', lb, ub)
assert_allclose(h_adjusted, np.abs(h))
assert_(np.all(~one_sided))
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 2, '2-sided', lb, ub)
assert_allclose(h_adjusted, np.array([1, -1, 1]) * 1e-1)
assert_equal(one_sided, np.array([False, True, True]))
def test_tight_bounds(self):
lb = np.array([-0.03, -0.03])
ub = np.array([0.05, 0.05])
x0 = np.array([0.0, 0.03])
h = np.array([-0.1, -0.1])
h_adjusted, _ = _adjust_scheme_to_bounds(x0, h, 1, '1-sided', lb, ub)
assert_allclose(h_adjusted, np.array([0.05, -0.06]))
h_adjusted, _ = _adjust_scheme_to_bounds(x0, h, 2, '1-sided', lb, ub)
assert_allclose(h_adjusted, np.array([0.025, -0.03]))
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 1, '2-sided', lb, ub)
assert_allclose(h_adjusted, np.array([0.03, -0.03]))
assert_equal(one_sided, np.array([False, True]))
h_adjusted, one_sided = _adjust_scheme_to_bounds(
x0, h, 2, '2-sided', lb, ub)
assert_allclose(h_adjusted, np.array([0.015, -0.015]))
assert_equal(one_sided, np.array([False, True]))
class TestApproxDerivativesDense(object):
def fun_scalar_scalar(self, x):
return np.sinh(x)
def jac_scalar_scalar(self, x):
return np.cosh(x)
def fun_scalar_vector(self, x):
return np.array([x[0]**2, np.tan(x[0]), np.exp(x[0])])
def jac_scalar_vector(self, x):
return np.array(
[2 * x[0], np.cos(x[0]) ** -2, np.exp(x[0])]).reshape(-1, 1)
def fun_vector_scalar(self, x):
return np.sin(x[0] * x[1]) * np.log(x[0])
def wrong_dimensions_fun(self, x):
return np.array([x**2, np.tan(x), np.exp(x)])
def jac_vector_scalar(self, x):
return np.array([
x[1] * np.cos(x[0] * x[1]) * np.log(x[0]) +
np.sin(x[0] * x[1]) / x[0],
x[0] * np.cos(x[0] * x[1]) * np.log(x[0])
])
def fun_vector_vector(self, x):
return np.array([
x[0] * np.sin(x[1]),
x[1] * np.cos(x[0]),
x[0] ** 3 * x[1] ** -0.5
])
def jac_vector_vector(self, x):
return np.array([
[np.sin(x[1]), x[0] * np.cos(x[1])],
[-x[1] * np.sin(x[0]), np.cos(x[0])],
[3 * x[0] ** 2 * x[1] ** -0.5, -0.5 * x[0] ** 3 * x[1] ** -1.5]
])
def fun_parametrized(self, x, c0, c1=1.0):
return np.array([np.exp(c0 * x[0]), np.exp(c1 * x[1])])
def jac_parametrized(self, x, c0, c1=0.1):
return np.array([
[c0 * np.exp(c0 * x[0]), 0],
[0, c1 * np.exp(c1 * x[1])]
])
def fun_with_nan(self, x):
return x if np.abs(x) <= 1e-8 else np.nan
def jac_with_nan(self, x):
return 1.0 if np.abs(x) <= 1e-8 else np.nan
def fun_zero_jacobian(self, x):
return np.array([x[0] * x[1], np.cos(x[0] * x[1])])
def jac_zero_jacobian(self, x):
return np.array([
[x[1], x[0]],
[-x[1] * np.sin(x[0] * x[1]), -x[0] * np.sin(x[0] * x[1])]
])
def fun_non_numpy(self, x):
return math.exp(x)
def jac_non_numpy(self, x):
return math.exp(x)
def test_scalar_scalar(self):
x0 = 1.0
jac_diff_2 = approx_derivative(self.fun_scalar_scalar, x0,
method='2-point')
jac_diff_3 = approx_derivative(self.fun_scalar_scalar, x0)
jac_diff_4 = approx_derivative(self.fun_scalar_scalar, x0,
method='cs')
jac_true = self.jac_scalar_scalar(x0)
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-9)
assert_allclose(jac_diff_4, jac_true, rtol=1e-12)
def test_scalar_vector(self):
x0 = 0.5
jac_diff_2 = approx_derivative(self.fun_scalar_vector, x0,
method='2-point')
jac_diff_3 = approx_derivative(self.fun_scalar_vector, x0)
jac_diff_4 = approx_derivative(self.fun_scalar_vector, x0,
method='cs')
jac_true = self.jac_scalar_vector(np.atleast_1d(x0))
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-9)
assert_allclose(jac_diff_4, jac_true, rtol=1e-12)
def test_vector_scalar(self):
x0 = np.array([100.0, -0.5])
jac_diff_2 = approx_derivative(self.fun_vector_scalar, x0,
method='2-point')
jac_diff_3 = approx_derivative(self.fun_vector_scalar, x0)
jac_diff_4 = approx_derivative(self.fun_vector_scalar, x0,
method='cs')
jac_true = self.jac_vector_scalar(x0)
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-7)
assert_allclose(jac_diff_4, jac_true, rtol=1e-12)
def test_vector_vector(self):
x0 = np.array([-100.0, 0.2])
jac_diff_2 = approx_derivative(self.fun_vector_vector, x0,
method='2-point')
jac_diff_3 = approx_derivative(self.fun_vector_vector, x0)
jac_diff_4 = approx_derivative(self.fun_vector_vector, x0,
method='cs')
jac_true = self.jac_vector_vector(x0)
assert_allclose(jac_diff_2, jac_true, rtol=1e-5)
assert_allclose(jac_diff_3, jac_true, rtol=1e-6)
assert_allclose(jac_diff_4, jac_true, rtol=1e-12)
def test_wrong_dimensions(self):
x0 = 1.0
assert_raises(RuntimeError, approx_derivative,
self.wrong_dimensions_fun, x0)
f0 = self.wrong_dimensions_fun(np.atleast_1d(x0))
assert_raises(ValueError, approx_derivative,
self.wrong_dimensions_fun, x0, f0=f0)
def test_custom_rel_step(self):
x0 = np.array([-0.1, 0.1])
jac_diff_2 = approx_derivative(self.fun_vector_vector, x0,
method='2-point', rel_step=1e-4)
jac_diff_3 = approx_derivative(self.fun_vector_vector, x0,
rel_step=1e-4)
jac_true = self.jac_vector_vector(x0)
assert_allclose(jac_diff_2, jac_true, rtol=1e-2)
assert_allclose(jac_diff_3, jac_true, rtol=1e-4)
def test_options(self):
x0 = np.array([1.0, 1.0])
c0 = -1.0
c1 = 1.0
lb = 0.0
ub = 2.0
f0 = self.fun_parametrized(x0, c0, c1=c1)
rel_step = np.array([-1e-6, 1e-7])
jac_true = self.jac_parametrized(x0, c0, c1)
jac_diff_2 = approx_derivative(
self.fun_parametrized, x0, method='2-point', rel_step=rel_step,
f0=f0, args=(c0,), kwargs=dict(c1=c1), bounds=(lb, ub))
jac_diff_3 = approx_derivative(
self.fun_parametrized, x0, rel_step=rel_step,
f0=f0, args=(c0,), kwargs=dict(c1=c1), bounds=(lb, ub))
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-9)
def test_with_bounds_2_point(self):
lb = -np.ones(2)
ub = np.ones(2)
x0 = np.array([-2.0, 0.2])
assert_raises(ValueError, approx_derivative,
self.fun_vector_vector, x0, bounds=(lb, ub))
x0 = np.array([-1.0, 1.0])
jac_diff = approx_derivative(self.fun_vector_vector, x0,
method='2-point', bounds=(lb, ub))
jac_true = self.jac_vector_vector(x0)
assert_allclose(jac_diff, jac_true, rtol=1e-6)
def test_with_bounds_3_point(self):
lb = np.array([1.0, 1.0])
ub = np.array([2.0, 2.0])
x0 = np.array([1.0, 2.0])
jac_true = self.jac_vector_vector(x0)
jac_diff = approx_derivative(self.fun_vector_vector, x0)
assert_allclose(jac_diff, jac_true, rtol=1e-9)
jac_diff = approx_derivative(self.fun_vector_vector, x0,
bounds=(lb, np.inf))
assert_allclose(jac_diff, jac_true, rtol=1e-9)
jac_diff = approx_derivative(self.fun_vector_vector, x0,
bounds=(-np.inf, ub))
assert_allclose(jac_diff, jac_true, rtol=1e-9)
jac_diff = approx_derivative(self.fun_vector_vector, x0,
bounds=(lb, ub))
assert_allclose(jac_diff, jac_true, rtol=1e-9)
def test_tight_bounds(self):
x0 = np.array([10.0, 10.0])
lb = x0 - 3e-9
ub = x0 + 2e-9
jac_true = self.jac_vector_vector(x0)
jac_diff = approx_derivative(
self.fun_vector_vector, x0, method='2-point', bounds=(lb, ub))
assert_allclose(jac_diff, jac_true, rtol=1e-6)
jac_diff = approx_derivative(
self.fun_vector_vector, x0, method='2-point',
rel_step=1e-6, bounds=(lb, ub))
assert_allclose(jac_diff, jac_true, rtol=1e-6)
jac_diff = approx_derivative(
self.fun_vector_vector, x0, bounds=(lb, ub))
assert_allclose(jac_diff, jac_true, rtol=1e-6)
jac_diff = approx_derivative(
self.fun_vector_vector, x0, rel_step=1e-6, bounds=(lb, ub))
assert_allclose(jac_true, jac_diff, rtol=1e-6)
def test_bound_switches(self):
lb = -1e-8
ub = 1e-8
x0 = 0.0
jac_true = self.jac_with_nan(x0)
jac_diff_2 = approx_derivative(
self.fun_with_nan, x0, method='2-point', rel_step=1e-6,
bounds=(lb, ub))
jac_diff_3 = approx_derivative(
self.fun_with_nan, x0, rel_step=1e-6, bounds=(lb, ub))
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-9)
x0 = 1e-8
jac_true = self.jac_with_nan(x0)
jac_diff_2 = approx_derivative(
self.fun_with_nan, x0, method='2-point', rel_step=1e-6,
bounds=(lb, ub))
jac_diff_3 = approx_derivative(
self.fun_with_nan, x0, rel_step=1e-6, bounds=(lb, ub))
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-9)
def test_non_numpy(self):
x0 = 1.0
jac_true = self.jac_non_numpy(x0)
jac_diff_2 = approx_derivative(self.jac_non_numpy, x0,
method='2-point')
jac_diff_3 = approx_derivative(self.jac_non_numpy, x0)
assert_allclose(jac_diff_2, jac_true, rtol=1e-6)
assert_allclose(jac_diff_3, jac_true, rtol=1e-8)
# math.exp cannot handle complex arguments, hence this raises
assert_raises(TypeError, approx_derivative, self.jac_non_numpy, x0,
**dict(method='cs'))
def test_check_derivative(self):
x0 = np.array([-10.0, 10])
accuracy = check_derivative(self.fun_vector_vector,
self.jac_vector_vector, x0)
assert_(accuracy < 1e-9)
accuracy = check_derivative(self.fun_vector_vector,
self.jac_vector_vector, x0)
assert_(accuracy < 1e-6)
x0 = np.array([0.0, 0.0])
accuracy = check_derivative(self.fun_zero_jacobian,
self.jac_zero_jacobian, x0)
assert_(accuracy == 0)
accuracy = check_derivative(self.fun_zero_jacobian,
self.jac_zero_jacobian, x0)
assert_(accuracy == 0)
class TestApproxDerivativeSparse(object):
# Example from Numerical Optimization 2nd edition, p. 198.
def setup_method(self):
np.random.seed(0)
self.n = 50
self.lb = -0.1 * (1 + np.arange(self.n))
self.ub = 0.1 * (1 + np.arange(self.n))
self.x0 = np.empty(self.n)
self.x0[::2] = (1 - 1e-7) * self.lb[::2]
self.x0[1::2] = (1 - 1e-7) * self.ub[1::2]
self.J_true = self.jac(self.x0)
def fun(self, x):
e = x[1:]**3 - x[:-1]**2
return np.hstack((0, 3 * e)) + np.hstack((2 * e, 0))
def jac(self, x):
n = x.size
J = np.zeros((n, n))
J[0, 0] = -4 * x[0]
J[0, 1] = 6 * x[1]**2
for i in range(1, n - 1):
J[i, i - 1] = -6 * x[i-1]
J[i, i] = 9 * x[i]**2 - 4 * x[i]
J[i, i + 1] = 6 * x[i+1]**2
J[-1, -1] = 9 * x[-1]**2
J[-1, -2] = -6 * x[-2]
return J
def structure(self, n):
A = np.zeros((n, n), dtype=int)
A[0, 0] = 1
A[0, 1] = 1
for i in range(1, n - 1):
A[i, i - 1: i + 2] = 1
A[-1, -1] = 1
A[-1, -2] = 1
return A
def test_all(self):
A = self.structure(self.n)
order = np.arange(self.n)
groups_1 = group_columns(A, order)
np.random.shuffle(order)
groups_2 = group_columns(A, order)
for method, groups, l, u in product(
['2-point', '3-point', 'cs'], [groups_1, groups_2],
[-np.inf, self.lb], [np.inf, self.ub]):
J = approx_derivative(self.fun, self.x0, method=method,
bounds=(l, u), sparsity=(A, groups))
assert_(isinstance(J, csr_matrix))
assert_allclose(J.toarray(), self.J_true, rtol=1e-6)
rel_step = 1e-8 * np.ones_like(self.x0)
rel_step[::2] *= -1
J = approx_derivative(self.fun, self.x0, method=method,
rel_step=rel_step, sparsity=(A, groups))
assert_allclose(J.toarray(), self.J_true, rtol=1e-5)
def test_no_precomputed_groups(self):
A = self.structure(self.n)
J = approx_derivative(self.fun, self.x0, sparsity=A)
assert_allclose(J.toarray(), self.J_true, rtol=1e-6)
def test_equivalence(self):
structure = np.ones((self.n, self.n), dtype=int)
groups = np.arange(self.n)
for method in ['2-point', '3-point', 'cs']:
J_dense = approx_derivative(self.fun, self.x0, method=method)
J_sparse = approx_derivative(
self.fun, self.x0, sparsity=(structure, groups), method=method)
assert_equal(J_dense, J_sparse.toarray())
def test_check_derivative(self):
def jac(x):
return csr_matrix(self.jac(x))
accuracy = check_derivative(self.fun, jac, self.x0,
bounds=(self.lb, self.ub))
assert_(accuracy < 1e-9)
accuracy = check_derivative(self.fun, jac, self.x0,
bounds=(self.lb, self.ub))
assert_(accuracy < 1e-9)
class TestApproxDerivativeLinearOperator(object):
def fun_scalar_scalar(self, x):
return np.sinh(x)
def jac_scalar_scalar(self, x):
return np.cosh(x)
def fun_scalar_vector(self, x):
return np.array([x[0]**2, np.tan(x[0]), np.exp(x[0])])
def jac_scalar_vector(self, x):
return np.array(
[2 * x[0], np.cos(x[0]) ** -2, np.exp(x[0])]).reshape(-1, 1)
def fun_vector_scalar(self, x):
return np.sin(x[0] * x[1]) * np.log(x[0])
def jac_vector_scalar(self, x):
return np.array([
x[1] * np.cos(x[0] * x[1]) * np.log(x[0]) +
np.sin(x[0] * x[1]) / x[0],
x[0] * np.cos(x[0] * x[1]) * np.log(x[0])
])
def fun_vector_vector(self, x):
return np.array([
x[0] * np.sin(x[1]),
x[1] * np.cos(x[0]),
x[0] ** 3 * x[1] ** -0.5
])
def jac_vector_vector(self, x):
return np.array([
[np.sin(x[1]), x[0] * np.cos(x[1])],
[-x[1] * np.sin(x[0]), np.cos(x[0])],
[3 * x[0] ** 2 * x[1] ** -0.5, -0.5 * x[0] ** 3 * x[1] ** -1.5]
])
def test_scalar_scalar(self):
x0 = 1.0
jac_diff_2 = approx_derivative(self.fun_scalar_scalar, x0,
method='2-point',
as_linear_operator=True)
jac_diff_3 = approx_derivative(self.fun_scalar_scalar, x0,
as_linear_operator=True)
jac_diff_4 = approx_derivative(self.fun_scalar_scalar, x0,
method='cs',
as_linear_operator=True)
jac_true = self.jac_scalar_scalar(x0)
np.random.seed(1)
for i in range(10):
p = np.random.uniform(-10, 10, size=(1,))
assert_allclose(jac_diff_2.dot(p), jac_true*p,
rtol=1e-5)
assert_allclose(jac_diff_3.dot(p), jac_true*p,
rtol=5e-6)
assert_allclose(jac_diff_4.dot(p), jac_true*p,
rtol=5e-6)
def test_scalar_vector(self):
x0 = 0.5
jac_diff_2 = approx_derivative(self.fun_scalar_vector, x0,
method='2-point',
as_linear_operator=True)
jac_diff_3 = approx_derivative(self.fun_scalar_vector, x0,
as_linear_operator=True)
jac_diff_4 = approx_derivative(self.fun_scalar_vector, x0,
method='cs',
as_linear_operator=True)
jac_true = self.jac_scalar_vector(np.atleast_1d(x0))
np.random.seed(1)
for i in range(10):
p = np.random.uniform(-10, 10, size=(1,))
assert_allclose(jac_diff_2.dot(p), jac_true.dot(p),
rtol=1e-5)
assert_allclose(jac_diff_3.dot(p), jac_true.dot(p),
rtol=5e-6)
assert_allclose(jac_diff_4.dot(p), jac_true.dot(p),
rtol=5e-6)
def test_vector_scalar(self):
x0 = np.array([100.0, -0.5])
jac_diff_2 = approx_derivative(self.fun_vector_scalar, x0,
method='2-point',
as_linear_operator=True)
jac_diff_3 = approx_derivative(self.fun_vector_scalar, x0,
as_linear_operator=True)
jac_diff_4 = approx_derivative(self.fun_vector_scalar, x0,
method='cs',
as_linear_operator=True)
jac_true = self.jac_vector_scalar(x0)
np.random.seed(1)
for i in range(10):
p = np.random.uniform(-10, 10, size=x0.shape)
assert_allclose(jac_diff_2.dot(p), np.atleast_1d(jac_true.dot(p)),
rtol=1e-5)
assert_allclose(jac_diff_3.dot(p), np.atleast_1d(jac_true.dot(p)),
rtol=5e-6)
assert_allclose(jac_diff_4.dot(p), np.atleast_1d(jac_true.dot(p)),
rtol=1e-7)
def test_vector_vector(self):
x0 = np.array([-100.0, 0.2])
jac_diff_2 = approx_derivative(self.fun_vector_vector, x0,
method='2-point',
as_linear_operator=True)
jac_diff_3 = approx_derivative(self.fun_vector_vector, x0,
as_linear_operator=True)
jac_diff_4 = approx_derivative(self.fun_vector_vector, x0,
method='cs',
as_linear_operator=True)
jac_true = self.jac_vector_vector(x0)
np.random.seed(1)
for i in range(10):
p = np.random.uniform(-10, 10, size=x0.shape)
assert_allclose(jac_diff_2.dot(p), jac_true.dot(p), rtol=1e-5)
assert_allclose(jac_diff_3.dot(p), jac_true.dot(p), rtol=1e-6)
assert_allclose(jac_diff_4.dot(p), jac_true.dot(p), rtol=1e-7)
def test_exception(self):
x0 = np.array([-100.0, 0.2])
assert_raises(ValueError, approx_derivative,
self.fun_vector_vector, x0,
method='2-point', bounds=(1, np.inf))
@@ -0,0 +1,239 @@
"""
Unit test for Linear Programming via Simplex Algorithm.
"""
# TODO: add tests for:
# https://github.com/scipy/scipy/issues/5400
# https://github.com/scipy/scipy/issues/6690
from __future__ import division, print_function, absolute_import
import numpy as np
from numpy.testing import (
assert_,
assert_allclose,
assert_equal)
from .test_linprog import magic_square
from scipy.optimize._remove_redundancy import _remove_redundancy
def setup_module():
np.random.seed(2017)
def _assert_success(
res,
desired_fun=None,
desired_x=None,
rtol=1e-7,
atol=1e-7):
# res: linprog result object
# desired_fun: desired objective function value or None
# desired_x: desired solution or None
assert_(res.success)
assert_equal(res.status, 0)
if desired_fun is not None:
assert_allclose(
res.fun,
desired_fun,
err_msg="converged to an unexpected objective value",
rtol=rtol,
atol=atol)
if desired_x is not None:
assert_allclose(
res.x,
desired_x,
err_msg="converged to an unexpected solution",
rtol=rtol,
atol=atol)
def test_no_redundancy():
m, n = 10, 10
A0 = np.random.rand(m, n)
b0 = np.random.rand(m)
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_allclose(A0, A1)
assert_allclose(b0, b1)
assert_equal(status, 0)
def test_infeasible_zero_row():
A = np.eye(3)
A[1, :] = 0
b = np.random.rand(3)
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 2)
def test_remove_zero_row():
A = np.eye(3)
A[1, :] = 0
b = np.random.rand(3)
b[1] = 0
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 0)
assert_allclose(A1, A[[0, 2], :])
assert_allclose(b1, b[[0, 2]])
def test_infeasible_m_gt_n():
m, n = 20, 10
A0 = np.random.rand(m, n)
b0 = np.random.rand(m)
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_equal(status, 2)
def test_infeasible_m_eq_n():
m, n = 10, 10
A0 = np.random.rand(m, n)
b0 = np.random.rand(m)
A0[-1, :] = 2 * A0[-2, :]
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_equal(status, 2)
def test_infeasible_m_lt_n():
m, n = 9, 10
A0 = np.random.rand(m, n)
b0 = np.random.rand(m)
A0[-1, :] = np.arange(m - 1).dot(A0[:-1])
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_equal(status, 2)
def test_m_gt_n():
np.random.seed(2032)
m, n = 20, 10
A0 = np.random.rand(m, n)
b0 = np.random.rand(m)
x = np.linalg.solve(A0[:n, :], b0[:n])
b0[n:] = A0[n:, :].dot(x)
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_equal(status, 0)
assert_equal(A1.shape[0], n)
assert_equal(np.linalg.matrix_rank(A1), n)
def test_m_gt_n_rank_deficient():
m, n = 20, 10
A0 = np.zeros((m, n))
A0[:, 0] = 1
b0 = np.ones(m)
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_equal(status, 0)
assert_allclose(A1, A0[0:1, :])
assert_allclose(b1, b0[0])
def test_m_lt_n_rank_deficient():
m, n = 9, 10
A0 = np.random.rand(m, n)
b0 = np.random.rand(m)
A0[-1, :] = np.arange(m - 1).dot(A0[:-1])
b0[-1] = np.arange(m - 1).dot(b0[:-1])
A1, b1, status, message = _remove_redundancy(A0, b0)
assert_equal(status, 0)
assert_equal(A1.shape[0], 8)
assert_equal(np.linalg.matrix_rank(A1), 8)
def test_dense1():
A = np.ones((6, 6))
A[0, :3] = 0
A[1, 3:] = 0
A[3:, ::2] = -1
A[3, :2] = 0
A[4, 2:] = 0
b = np.zeros(A.shape[0])
A2 = A[[0, 1, 3, 4], :]
b2 = np.zeros(4)
A1, b1, status, message = _remove_redundancy(A, b)
assert_allclose(A1, A2)
assert_allclose(b1, b2)
assert_equal(status, 0)
def test_dense2():
A = np.eye(6)
A[-2, -1] = 1
A[-1, :] = 1
b = np.zeros(A.shape[0])
A1, b1, status, message = _remove_redundancy(A, b)
assert_allclose(A1, A[:-1, :])
assert_allclose(b1, b[:-1])
assert_equal(status, 0)
def test_dense3():
A = np.eye(6)
A[-2, -1] = 1
A[-1, :] = 1
b = np.random.rand(A.shape[0])
b[-1] = np.sum(b[:-1])
A1, b1, status, message = _remove_redundancy(A, b)
assert_allclose(A1, A[:-1, :])
assert_allclose(b1, b[:-1])
assert_equal(status, 0)
def test_m_gt_n_sparse():
np.random.seed(2013)
m, n = 20, 5
p = 0.1
A = np.random.rand(m, n)
A[np.random.rand(m, n) > p] = 0
rank = np.linalg.matrix_rank(A)
b = np.zeros(A.shape[0])
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 0)
assert_equal(A1.shape[0], rank)
assert_equal(np.linalg.matrix_rank(A1), rank)
def test_m_lt_n_sparse():
np.random.seed(2017)
m, n = 20, 50
p = 0.05
A = np.random.rand(m, n)
A[np.random.rand(m, n) > p] = 0
rank = np.linalg.matrix_rank(A)
b = np.zeros(A.shape[0])
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 0)
assert_equal(A1.shape[0], rank)
assert_equal(np.linalg.matrix_rank(A1), rank)
def test_m_eq_n_sparse():
np.random.seed(2017)
m, n = 100, 100
p = 0.01
A = np.random.rand(m, n)
A[np.random.rand(m, n) > p] = 0
rank = np.linalg.matrix_rank(A)
b = np.zeros(A.shape[0])
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 0)
assert_equal(A1.shape[0], rank)
assert_equal(np.linalg.matrix_rank(A1), rank)
def test_magic_square():
A, b, c, numbers = magic_square(3)
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 0)
assert_equal(A1.shape[0], 23)
assert_equal(np.linalg.matrix_rank(A1), 23)
def test_magic_square2():
A, b, c, numbers = magic_square(4)
A1, b1, status, message = _remove_redundancy(A, b)
assert_equal(status, 0)
assert_equal(A1.shape[0], 39)
assert_equal(np.linalg.matrix_rank(A1), 39)
@@ -0,0 +1,71 @@
"""
Unit tests for optimization routines from _root.py.
"""
from __future__ import division, print_function, absolute_import
from numpy.testing import assert_
from pytest import raises as assert_raises
import numpy as np
from scipy.optimize import root
class TestRoot(object):
def test_tol_parameter(self):
# Check that the minimize() tol= argument does something
def func(z):
x, y = z
return np.array([x**3 - 1, y**3 - 1])
def dfunc(z):
x, y = z
return np.array([[3*x**2, 0], [0, 3*y**2]])
for method in ['hybr', 'lm', 'broyden1', 'broyden2', 'anderson',
'diagbroyden', 'krylov']:
if method in ('linearmixing', 'excitingmixing'):
# doesn't converge
continue
if method in ('hybr', 'lm'):
jac = dfunc
else:
jac = None
sol1 = root(func, [1.1,1.1], jac=jac, tol=1e-4, method=method)
sol2 = root(func, [1.1,1.1], jac=jac, tol=0.5, method=method)
msg = "%s: %s vs. %s" % (method, func(sol1.x), func(sol2.x))
assert_(sol1.success, msg)
assert_(sol2.success, msg)
assert_(abs(func(sol1.x)).max() < abs(func(sol2.x)).max(),
msg)
def test_minimize_scalar_coerce_args_param(self):
# github issue #3503
def func(z, f=1):
x, y = z
return np.array([x**3 - 1, y**3 - f])
root(func, [1.1, 1.1], args=1.5)
def test_f_size(self):
# gh8320
# check that decreasing the size of the returned array raises an error
# and doesn't segfault
class fun(object):
def __init__(self):
self.count = 0
def __call__(self, x):
self.count += 1
if not (self.count % 5):
ret = x[0] + 0.5 * (x[0] - x[1]) ** 3 - 1.0
else:
ret = ([x[0] + 0.5 * (x[0] - x[1]) ** 3 - 1.0,
0.5 * (x[1] - x[0]) ** 3 + x[1]])
return ret
F = fun()
with assert_raises(ValueError):
sol = root(F, [0.1, 0.0], method='lm')
@@ -0,0 +1,748 @@
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 minimiser 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 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)]"""
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):
# """1D tabletop function"""
# run_test(test11_1)
class TestShgoSimplicialTestFunctions(object):
"""
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=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,) # No. 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]:
res = shgo(test.f, test.bounds, iters=1,
sampling_method='simplicial',
callback=callback_func, options={'disp': True})
res = 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]:
res = shgo(test.f, test.bounds, iters=1, sampling_method='sobol',
callback=callback_func, options={'disp': True})
res = 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 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')
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
SHGOc = 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"""
SHGOc = 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}
res = 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}
res = 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}
res = 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')
# 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)
@@ -0,0 +1,210 @@
from __future__ import division, absolute_import, print_function
import itertools
import numpy as np
from numpy import exp
from numpy.testing import assert_, assert_equal
from scipy.optimize import root
def test_performance():
# Compare performance results to those listed in
# [Cheng & Li, IMA J. Num. An. 29, 814 (2008)]
# and
# [W. La Cruz, J.M. Martinez, M. Raydan, Math. Comp. 75, 1429 (2006)].
# and those produced by dfsane.f from M. Raydan's website.
#
# Where the results disagree, the largest limits are taken.
e_a = 1e-5
e_r = 1e-4
table_1 = [
dict(F=F_1, x0=x0_1, n=1000, nit=5, nfev=5),
dict(F=F_1, x0=x0_1, n=10000, nit=2, nfev=2),
dict(F=F_2, x0=x0_2, n=500, nit=11, nfev=11),
dict(F=F_2, x0=x0_2, n=2000, nit=11, nfev=11),
# dict(F=F_4, x0=x0_4, n=999, nit=243, nfev=1188), removed: too sensitive to rounding errors
dict(F=F_6, x0=x0_6, n=100, nit=6, nfev=6), # Results from dfsane.f; papers list nit=3, nfev=3
dict(F=F_7, x0=x0_7, n=99, nit=23, nfev=29), # Must have n%3==0, typo in papers?
dict(F=F_7, x0=x0_7, n=999, nit=23, nfev=29), # Must have n%3==0, typo in papers?
dict(F=F_9, x0=x0_9, n=100, nit=12, nfev=18), # Results from dfsane.f; papers list nit=nfev=6?
dict(F=F_9, x0=x0_9, n=1000, nit=12, nfev=18),
dict(F=F_10, x0=x0_10, n=1000, nit=5, nfev=5), # Results from dfsane.f; papers list nit=2, nfev=12
]
# Check also scaling invariance
for xscale, yscale, line_search in itertools.product([1.0, 1e-10, 1e10], [1.0, 1e-10, 1e10],
['cruz', 'cheng']):
for problem in table_1:
n = problem['n']
func = lambda x, n: yscale*problem['F'](x/xscale, n)
args = (n,)
x0 = problem['x0'](n) * xscale
fatol = np.sqrt(n) * e_a * yscale + e_r * np.linalg.norm(func(x0, n))
sigma_eps = 1e-10 * min(yscale/xscale, xscale/yscale)
sigma_0 = xscale/yscale
with np.errstate(over='ignore'):
sol = root(func, x0, args=args,
options=dict(ftol=0, fatol=fatol, maxfev=problem['nfev'] + 1,
sigma_0=sigma_0, sigma_eps=sigma_eps,
line_search=line_search),
method='DF-SANE')
err_msg = repr([xscale, yscale, line_search, problem, np.linalg.norm(func(sol.x, n)),
fatol, sol.success, sol.nit, sol.nfev])
assert_(sol.success, err_msg)
assert_(sol.nfev <= problem['nfev'] + 1, err_msg) # nfev+1: dfsane.f doesn't count first eval
assert_(sol.nit <= problem['nit'], err_msg)
assert_(np.linalg.norm(func(sol.x, n)) <= fatol, err_msg)
def test_complex():
def func(z):
return z**2 - 1 + 2j
x0 = 2.0j
ftol = 1e-4
sol = root(func, x0, tol=ftol, method='DF-SANE')
assert_(sol.success)
f0 = np.linalg.norm(func(x0))
fx = np.linalg.norm(func(sol.x))
assert_(fx <= ftol*f0)
def test_linear_definite():
# The DF-SANE paper proves convergence for "strongly isolated"
# solutions.
#
# For linear systems F(x) = A x - b = 0, with A positive or
# negative definite, the solution is strongly isolated.
def check_solvability(A, b, line_search='cruz'):
func = lambda x: A.dot(x) - b
xp = np.linalg.solve(A, b)
eps = np.linalg.norm(func(xp)) * 1e3
sol = root(func, b, options=dict(fatol=eps, ftol=0, maxfev=17523, line_search=line_search),
method='DF-SANE')
assert_(sol.success)
assert_(np.linalg.norm(func(sol.x)) <= eps)
n = 90
# Test linear pos.def. system
np.random.seed(1234)
A = np.arange(n*n).reshape(n, n)
A = A + n*n * np.diag(1 + np.arange(n))
assert_(np.linalg.eigvals(A).min() > 0)
b = np.arange(n) * 1.0
check_solvability(A, b, 'cruz')
check_solvability(A, b, 'cheng')
# Test linear neg.def. system
check_solvability(-A, b, 'cruz')
check_solvability(-A, b, 'cheng')
def test_shape():
def f(x, arg):
return x - arg
for dt in [float, complex]:
x = np.zeros([2,2])
arg = np.ones([2,2], dtype=dt)
sol = root(f, x, args=(arg,), method='DF-SANE')
assert_(sol.success)
assert_equal(sol.x.shape, x.shape)
# Some of the test functions and initial guesses listed in
# [W. La Cruz, M. Raydan. Optimization Methods and Software, 18, 583 (2003)]
def F_1(x, n):
g = np.zeros([n])
i = np.arange(2, n+1)
g[0] = exp(x[0] - 1) - 1
g[1:] = i*(exp(x[1:] - 1) - x[1:])
return g
def x0_1(n):
x0 = np.empty([n])
x0.fill(n/(n-1))
return x0
def F_2(x, n):
g = np.zeros([n])
i = np.arange(2, n+1)
g[0] = exp(x[0]) - 1
g[1:] = 0.1*i*(exp(x[1:]) + x[:-1] - 1)
return g
def x0_2(n):
x0 = np.empty([n])
x0.fill(1/n**2)
return x0
def F_4(x, n):
assert_equal(n % 3, 0)
g = np.zeros([n])
# Note: the first line is typoed in some of the references;
# correct in original [Gasparo, Optimization Meth. 13, 79 (2000)]
g[::3] = 0.6 * x[::3] + 1.6 * x[1::3]**3 - 7.2 * x[1::3]**2 + 9.6 * x[1::3] - 4.8
g[1::3] = 0.48 * x[::3] - 0.72 * x[1::3]**3 + 3.24 * x[1::3]**2 - 4.32 * x[1::3] - x[2::3] + 0.2 * x[2::3]**3 + 2.16
g[2::3] = 1.25 * x[2::3] - 0.25*x[2::3]**3
return g
def x0_4(n):
assert_equal(n % 3, 0)
x0 = np.array([-1, 1/2, -1] * (n//3))
return x0
def F_6(x, n):
c = 0.9
mu = (np.arange(1, n+1) - 0.5)/n
return x - 1/(1 - c/(2*n) * (mu[:,None]*x / (mu[:,None] + mu)).sum(axis=1))
def x0_6(n):
return np.ones([n])
def F_7(x, n):
assert_equal(n % 3, 0)
def phi(t):
v = 0.5*t - 2
v[t > -1] = ((-592*t**3 + 888*t**2 + 4551*t - 1924)/1998)[t > -1]
v[t >= 2] = (0.5*t + 2)[t >= 2]
return v
g = np.zeros([n])
g[::3] = 1e4 * x[1::3]**2 - 1
g[1::3] = exp(-x[::3]) + exp(-x[1::3]) - 1.0001
g[2::3] = phi(x[2::3])
return g
def x0_7(n):
assert_equal(n % 3, 0)
return np.array([1e-3, 18, 1] * (n//3))
def F_9(x, n):
g = np.zeros([n])
i = np.arange(2, n)
g[0] = x[0]**3/3 + x[1]**2/2
g[1:-1] = -x[1:-1]**2/2 + i*x[1:-1]**3/3 + x[2:]**2/2
g[-1] = -x[-1]**2/2 + n*x[-1]**3/3
return g
def x0_9(n):
return np.ones([n])
def F_10(x, n):
return np.log(1 + x) - x/n
def x0_10(n):
return np.ones([n])
@@ -0,0 +1,115 @@
from __future__ import division, print_function, absolute_import
import math
import numpy as np
from numpy.testing import assert_allclose, assert_
from scipy.optimize import fmin_cobyla, minimize
class TestCobyla(object):
def setup_method(self):
self.x0 = [4.95, 0.66]
self.solution = [math.sqrt(25 - (2.0/3)**2), 2.0/3]
self.opts = {'disp': False, 'rhobeg': 1, 'tol': 1e-5,
'maxiter': 100}
def fun(self, x):
return x[0]**2 + abs(x[1])**3
def con1(self, x):
return x[0]**2 + x[1]**2 - 25
def con2(self, x):
return -self.con1(x)
def test_simple(self):
# use disp=True as smoke test for gh-8118
x = fmin_cobyla(self.fun, self.x0, [self.con1, self.con2], rhobeg=1,
rhoend=1e-5, maxfun=100, disp=True)
assert_allclose(x, self.solution, atol=1e-4)
def test_minimize_simple(self):
# Minimize with method='COBYLA'
cons = ({'type': 'ineq', 'fun': self.con1},
{'type': 'ineq', 'fun': self.con2})
sol = minimize(self.fun, self.x0, method='cobyla', constraints=cons,
options=self.opts)
assert_allclose(sol.x, self.solution, atol=1e-4)
assert_(sol.success, sol.message)
assert_(sol.maxcv < 1e-5, sol)
assert_(sol.nfev < 70, sol)
assert_(sol.fun < self.fun(self.solution) + 1e-3, sol)
def test_minimize_constraint_violation(self):
np.random.seed(1234)
pb = np.random.rand(10, 10)
spread = np.random.rand(10)
def p(w):
return pb.dot(w)
def f(w):
return -(w * spread).sum()
def c1(w):
return 500 - abs(p(w)).sum()
def c2(w):
return 5 - abs(p(w).sum())
def c3(w):
return 5 - abs(p(w)).max()
cons = ({'type': 'ineq', 'fun': c1},
{'type': 'ineq', 'fun': c2},
{'type': 'ineq', 'fun': c3})
w0 = np.zeros((10, 1))
sol = minimize(f, w0, method='cobyla', constraints=cons,
options={'catol': 1e-6})
assert_(sol.maxcv > 1e-6)
assert_(not sol.success)
def test_vector_constraints():
# test that fmin_cobyla and minimize can take a combination
# of constraints, some returning a number and others an array
def fun(x):
return (x[0] - 1)**2 + (x[1] - 2.5)**2
def fmin(x):
return fun(x) - 1
def cons1(x):
a = np.array([[1, -2, 2], [-1, -2, 6], [-1, 2, 2]])
return np.array([a[i, 0] * x[0] + a[i, 1] * x[1] +
a[i, 2] for i in range(len(a))])
def cons2(x):
return x # identity, acts as bounds x > 0
x0 = np.array([2, 0])
cons_list = [fun, cons1, cons2]
xsol = [1.4, 1.7]
fsol = 0.8
# testing fmin_cobyla
sol = fmin_cobyla(fun, x0, cons_list, rhoend=1e-5)
assert_allclose(sol, xsol, atol=1e-4)
sol = fmin_cobyla(fun, x0, fmin, rhoend=1e-5)
assert_allclose(fun(sol), 1, atol=1e-4)
# testing minimize
constraints = [{'type': 'ineq', 'fun': cons} for cons in cons_list]
sol = minimize(fun, x0, constraints=constraints, tol=1e-5)
assert_allclose(sol.x, xsol, atol=1e-4)
assert_(sol.success, sol.message)
assert_allclose(sol.fun, fsol, atol=1e-4)
constraints = {'type': 'ineq', 'fun': fmin}
sol = minimize(fun, x0, constraints=constraints, tol=1e-5)
assert_allclose(sol.fun, 1, atol=1e-4)
@@ -0,0 +1,270 @@
from __future__ import division, print_function, absolute_import
"""
Unit test for constraint conversion
"""
import numpy as np
from numpy.testing import (assert_, assert_array_almost_equal,
assert_allclose, assert_equal, TestCase)
import pytest
from scipy._lib._numpy_compat import suppress_warnings
from scipy.optimize import (NonlinearConstraint, LinearConstraint, Bounds,
OptimizeWarning, minimize, BFGS)
from .test_minimize_constrained import (Maratos, HyperbolicIneq, Rosenbrock,
IneqRosenbrock, EqIneqRosenbrock,
BoundedRosenbrock, Elec)
from scipy._lib._numpy_compat import _assert_warns, suppress_warnings
class TestOldToNew(object):
x0 = (2, 0)
bnds = ((0, None), (0, None))
method = "trust-constr"
def test_constraint_dictionary_1(self):
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
cons = ({'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2},
{'type': 'ineq', 'fun': lambda x: -x[0] - 2 * x[1] + 6},
{'type': 'ineq', 'fun': lambda x: -x[0] + 2 * x[1] + 2})
with suppress_warnings() as sup:
sup.filter(UserWarning, "delta_grad == 0.0")
res = minimize(fun, self.x0, method=self.method,
bounds=self.bnds, constraints=cons)
assert_allclose(res.x, [1.4, 1.7], rtol=1e-4)
assert_allclose(res.fun, 0.8, rtol=1e-4)
def test_constraint_dictionary_2(self):
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
cons = {'type': 'eq',
'fun': lambda x, p1, p2: p1*x[0] - p2*x[1],
'args': (1, 1.1),
'jac': lambda x, p1, p2: np.array([[p1, -p2]])}
with suppress_warnings() as sup:
sup.filter(UserWarning, "delta_grad == 0.0")
res = minimize(fun, self.x0, method=self.method,
bounds=self.bnds, constraints=cons)
assert_allclose(res.x, [1.7918552, 1.62895927])
assert_allclose(res.fun, 1.3857466063348418)
def test_constraint_dictionary_3(self):
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
cons = [{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2},
NonlinearConstraint(lambda x: x[0] - x[1], 0, 0)]
with suppress_warnings() as sup:
sup.filter(UserWarning, "delta_grad == 0.0")
res = minimize(fun, self.x0, method=self.method,
bounds=self.bnds, constraints=cons)
assert_allclose(res.x, [1.75, 1.75], rtol=1e-4)
assert_allclose(res.fun, 1.125, rtol=1e-4)
class TestNewToOld(object):
def test_multiple_constraint_objects(self):
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
x0 = [2, 0, 1]
coni = [] # only inequality constraints (can use cobyla)
methods = ["slsqp", "cobyla", "trust-constr"]
# mixed old and new
coni.append([{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2},
NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)])
coni.append([LinearConstraint([1, -2, 0], -2, np.inf),
NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)])
coni.append([NonlinearConstraint(lambda x: x[0] - 2 * x[1] + 2, 0, np.inf),
NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)])
for con in coni:
funs = {}
for method in methods:
with suppress_warnings() as sup:
sup.filter(UserWarning)
result = minimize(fun, x0, method=method, constraints=con)
funs[method] = result.fun
assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-4)
assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-4)
def test_individual_constraint_objects(self):
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
x0 = [2, 0, 1]
cone = [] # with equality constraints (can't use cobyla)
coni = [] # only inequality constraints (can use cobyla)
methods = ["slsqp", "cobyla", "trust-constr"]
# nonstandard data types for constraint equality bounds
cone.append(NonlinearConstraint(lambda x: x[0] - x[1], 1, 1))
cone.append(NonlinearConstraint(lambda x: x[0] - x[1], [1.21], [1.21]))
cone.append(NonlinearConstraint(lambda x: x[0] - x[1],
1.21, np.array([1.21])))
# multiple equalities
cone.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
1.21, 1.21)) # two same equalities
cone.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
[1.21, 1.4], [1.21, 1.4])) # two different equalities
cone.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
[1.21, 1.21], 1.21)) # equality specified two ways
cone.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
[1.21, -np.inf], [1.21, np.inf])) # equality + unbounded
# nonstandard data types for constraint inequality bounds
coni.append(NonlinearConstraint(lambda x: x[0] - x[1], 1.21, np.inf))
coni.append(NonlinearConstraint(lambda x: x[0] - x[1], [1.21], np.inf))
coni.append(NonlinearConstraint(lambda x: x[0] - x[1],
1.21, np.array([np.inf])))
coni.append(NonlinearConstraint(lambda x: x[0] - x[1], -np.inf, -3))
coni.append(NonlinearConstraint(lambda x: x[0] - x[1],
np.array(-np.inf), -3))
# multiple inequalities/equalities
coni.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
1.21, np.inf)) # two same inequalities
cone.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
[1.21, -np.inf], [1.21, 1.4])) # mixed equality/inequality
coni.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
[1.1, .8], [1.2, 1.4])) # bounded above and below
coni.append(NonlinearConstraint(
lambda x: [x[0] - x[1], x[1] - x[2]],
[-1.2, -1.4], [-1.1, -.8])) # - bounded above and below
# quick check of LinearConstraint class (very little new code to test)
cone.append(LinearConstraint([1, -1, 0], 1.21, 1.21))
cone.append(LinearConstraint([[1, -1, 0], [0, 1, -1]], 1.21, 1.21))
cone.append(LinearConstraint([[1, -1, 0], [0, 1, -1]],
[1.21, -np.inf], [1.21, 1.4]))
for con in coni:
funs = {}
for method in methods:
with suppress_warnings() as sup:
sup.filter(UserWarning)
result = minimize(fun, x0, method=method, constraints=con)
funs[method] = result.fun
assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3)
assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-3)
for con in cone:
funs = {}
for method in methods[::2]: # skip cobyla
with suppress_warnings() as sup:
sup.filter(UserWarning)
result = minimize(fun, x0, method=method, constraints=con)
funs[method] = result.fun
assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3)
class TestNewToOldSLSQP(object):
method = 'slsqp'
elec = Elec(n_electrons=2)
elec.x_opt = np.array([-0.58438468, 0.58438466, 0.73597047,
-0.73597044, 0.34180668, -0.34180667])
brock = BoundedRosenbrock()
brock.x_opt = [0, 0]
list_of_problems = [Maratos(),
HyperbolicIneq(),
Rosenbrock(),
IneqRosenbrock(),
EqIneqRosenbrock(),
elec,
brock
]
def test_list_of_problems(self):
for prob in self.list_of_problems:
with suppress_warnings() as sup:
sup.filter(UserWarning)
result = minimize(prob.fun, prob.x0,
method=self.method,
bounds=prob.bounds,
constraints=prob.constr)
assert_array_almost_equal(result.x, prob.x_opt, decimal=3)
def test_warn_mixed_constraints(self):
# warns about inefficiency of mixed equality/inequality constraints
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
cons = NonlinearConstraint(lambda x: [x[0]**2 - x[1], x[1] - x[2]],
[1.1, .8], [1.1, 1.4])
bnds = ((0, None), (0, None), (0, None))
with suppress_warnings() as sup:
sup.filter(UserWarning, "delta_grad == 0.0")
_assert_warns(OptimizeWarning, minimize, fun, (2, 0, 1),
method=self.method, bounds=bnds, constraints=cons)
def test_warn_ignored_options(self):
# warns about constraint options being ignored
fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
x0 = (2, 0, 1)
if self.method == "slsqp":
bnds = ((0, None), (0, None), (0, None))
else:
bnds = None
cons = NonlinearConstraint(lambda x: x[0], 2, np.inf)
res = minimize(fun, x0, method=self.method,
bounds=bnds, constraints=cons)
# no warnings without constraint options
assert_allclose(res.fun, 1)
cons = LinearConstraint([1, 0, 0], 2, np.inf)
res = minimize(fun, x0, method=self.method,
bounds=bnds, constraints=cons)
# no warnings without constraint options
assert_allclose(res.fun, 1)
cons = []
cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
keep_feasible=True))
cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
hess=BFGS()))
cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
finite_diff_jac_sparsity=42))
cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
finite_diff_rel_step=42))
cons.append(LinearConstraint([1, 0, 0], 2, np.inf,
keep_feasible=True))
for con in cons:
_assert_warns(OptimizeWarning, minimize, fun, x0,
method=self.method, bounds=bnds, constraints=cons)
class TestNewToOldCobyla(object):
method = 'cobyla'
list_of_problems = [
Elec(n_electrons=2),
Elec(n_electrons=4),
]
@pytest.mark.slow
def test_list_of_problems(self):
for prob in self.list_of_problems:
with suppress_warnings() as sup:
sup.filter(UserWarning)
truth = minimize(prob.fun, prob.x0,
method='trust-constr',
bounds=prob.bounds,
constraints=prob.constr)
result = minimize(prob.fun, prob.x0,
method=self.method,
bounds=prob.bounds,
constraints=prob.constr)
assert_allclose(result.fun, truth.fun, rtol=1e-3)
@@ -0,0 +1,132 @@
from __future__ import division, print_function, absolute_import
import pytest
import numpy as np
from numpy.testing import TestCase, assert_array_equal
import scipy.sparse as sps
from scipy.optimize._constraints import (
Bounds, LinearConstraint, NonlinearConstraint, PreparedConstraint,
new_bounds_to_old, old_bound_to_new, strict_bounds)
class TestStrictBounds(TestCase):
def test_scalarvalue_unique_enforce_feasibility(self):
m = 3
lb = 2
ub = 4
enforce_feasibility = False
strict_lb, strict_ub = strict_bounds(lb, ub,
enforce_feasibility,
m)
assert_array_equal(strict_lb, [-np.inf, -np.inf, -np.inf])
assert_array_equal(strict_ub, [np.inf, np.inf, np.inf])
enforce_feasibility = True
strict_lb, strict_ub = strict_bounds(lb, ub,
enforce_feasibility,
m)
assert_array_equal(strict_lb, [2, 2, 2])
assert_array_equal(strict_ub, [4, 4, 4])
def test_vectorvalue_unique_enforce_feasibility(self):
m = 3
lb = [1, 2, 3]
ub = [4, 5, 6]
enforce_feasibility = False
strict_lb, strict_ub = strict_bounds(lb, ub,
enforce_feasibility,
m)
assert_array_equal(strict_lb, [-np.inf, -np.inf, -np.inf])
assert_array_equal(strict_ub, [np.inf, np.inf, np.inf])
enforce_feasibility = True
strict_lb, strict_ub = strict_bounds(lb, ub,
enforce_feasibility,
m)
assert_array_equal(strict_lb, [1, 2, 3])
assert_array_equal(strict_ub, [4, 5, 6])
def test_scalarvalue_vector_enforce_feasibility(self):
m = 3
lb = 2
ub = 4
enforce_feasibility = [False, True, False]
strict_lb, strict_ub = strict_bounds(lb, ub,
enforce_feasibility,
m)
assert_array_equal(strict_lb, [-np.inf, 2, -np.inf])
assert_array_equal(strict_ub, [np.inf, 4, np.inf])
def test_vectorvalue_vector_enforce_feasibility(self):
m = 3
lb = [1, 2, 3]
ub = [4, 6, np.inf]
enforce_feasibility = [True, False, True]
strict_lb, strict_ub = strict_bounds(lb, ub,
enforce_feasibility,
m)
assert_array_equal(strict_lb, [1, -np.inf, 3])
assert_array_equal(strict_ub, [4, np.inf, np.inf])
def test_prepare_constraint_infeasible_x0():
lb = np.array([0, 20, 30])
ub = np.array([0.5, np.inf, 70])
x0 = np.array([1, 2, 3])
enforce_feasibility = np.array([False, True, True], dtype=bool)
bounds = Bounds(lb, ub, enforce_feasibility)
pytest.raises(ValueError, PreparedConstraint, bounds, x0)
x0 = np.array([1, 2, 3, 4])
A = np.array([[1, 2, 3, 4], [5, 0, 0, 6], [7, 0, 8, 0]])
enforce_feasibility = np.array([True, True, True], dtype=bool)
linear = LinearConstraint(A, -np.inf, 0, enforce_feasibility)
pytest.raises(ValueError, PreparedConstraint, linear, x0)
def fun(x):
return A.dot(x)
def jac(x):
return A
def hess(x, v):
return sps.csr_matrix((4, 4))
nonlinear = NonlinearConstraint(fun, -np.inf, 0, jac, hess,
enforce_feasibility)
pytest.raises(ValueError, PreparedConstraint, nonlinear, x0)
def test_new_bounds_to_old():
lb = np.array([-np.inf, 2, 3])
ub = np.array([3, np.inf, 10])
bounds = [(None, 3), (2, None), (3, 10)]
assert_array_equal(new_bounds_to_old(lb, ub, 3), bounds)
bounds_single_lb = [(-1, 3), (-1, None), (-1, 10)]
assert_array_equal(new_bounds_to_old(-1, ub, 3), bounds_single_lb)
bounds_no_lb = [(None, 3), (None, None), (None, 10)]
assert_array_equal(new_bounds_to_old(-np.inf, ub, 3), bounds_no_lb)
bounds_single_ub = [(None, 20), (2, 20), (3, 20)]
assert_array_equal(new_bounds_to_old(lb, 20, 3), bounds_single_ub)
bounds_no_ub = [(None, None), (2, None), (3, None)]
assert_array_equal(new_bounds_to_old(lb, np.inf, 3), bounds_no_ub)
bounds_single_both = [(1, 2), (1, 2), (1, 2)]
assert_array_equal(new_bounds_to_old(1, 2, 3), bounds_single_both)
bounds_no_both = [(None, None), (None, None), (None, None)]
assert_array_equal(new_bounds_to_old(-np.inf, np.inf, 3), bounds_no_both)
def test_old_bounds_to_new():
bounds = ([1, 2], (None, 3), (-1, None))
lb_true = np.array([1, -np.inf, -1])
ub_true = np.array([2, 3, np.inf])
lb, ub = old_bound_to_new(bounds)
assert_array_equal(lb, lb_true)
assert_array_equal(ub, ub_true)
@@ -0,0 +1,580 @@
from __future__ import division, print_function, absolute_import
import numpy as np
from numpy.testing import (TestCase, assert_array_almost_equal,
assert_array_equal, assert_)
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import LinearOperator
from scipy.optimize._differentiable_functions import (ScalarFunction,
VectorFunction,
LinearVectorFunction,
IdentityVectorFunction)
class ExScalarFunction:
def __init__(self):
self.nfev = 0
self.ngev = 0
self.nhev = 0
def fun(self, x):
self.nfev += 1
return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
def grad(self, x):
self.ngev += 1
return np.array([4*x[0]-1, 4*x[1]])
def hess(self, x):
self.nhev += 1
return 4*np.eye(2)
class TestScalarFunction(TestCase):
def test_finite_difference_grad(self):
ex = ExScalarFunction()
nfev = 0
ngev = 0
x0 = [1.0, 0.0]
analit = ScalarFunction(ex.fun, x0, (), ex.grad,
ex.hess, None, (-np.inf, np.inf))
nfev += 1
ngev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev, nfev)
approx = ScalarFunction(ex.fun, x0, (), '2-point',
ex.hess, None, (-np.inf, np.inf))
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(analit.f, approx.f)
assert_array_almost_equal(analit.g, approx.g)
x = [10, 0.3]
f_analit = analit.fun(x)
g_analit = analit.grad(x)
nfev += 1
ngev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
f_approx = approx.fun(x)
g_approx = approx.grad(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_almost_equal(f_analit, f_approx)
assert_array_almost_equal(g_analit, g_approx)
x = [2.0, 1.0]
g_analit = analit.grad(x)
ngev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
g_approx = approx.grad(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_almost_equal(g_analit, g_approx)
x = [2.5, 0.3]
f_analit = analit.fun(x)
g_analit = analit.grad(x)
nfev += 1
ngev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
f_approx = approx.fun(x)
g_approx = approx.grad(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_almost_equal(f_analit, f_approx)
assert_array_almost_equal(g_analit, g_approx)
x = [2, 0.3]
f_analit = analit.fun(x)
g_analit = analit.grad(x)
nfev += 1
ngev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
f_approx = approx.fun(x)
g_approx = approx.grad(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_almost_equal(f_analit, f_approx)
assert_array_almost_equal(g_analit, g_approx)
def test_finite_difference_hess_linear_operator(self):
ex = ExScalarFunction()
nfev = 0
ngev = 0
nhev = 0
x0 = [1.0, 0.0]
analit = ScalarFunction(ex.fun, x0, (), ex.grad,
ex.hess, None, (-np.inf, np.inf))
nfev += 1
ngev += 1
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev, nhev)
approx = ScalarFunction(ex.fun, x0, (), ex.grad,
'2-point', None, (-np.inf, np.inf))
assert_(isinstance(approx.H, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_equal(analit.f, approx.f)
assert_array_almost_equal(analit.g, approx.g)
assert_array_almost_equal(analit.H.dot(v), approx.H.dot(v))
nfev += 1
ngev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [2.0, 1.0]
H_analit = analit.hess(x)
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
H_approx = approx.hess(x)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v))
ngev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [2.1, 1.2]
H_analit = analit.hess(x)
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
H_approx = approx.hess(x)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v))
ngev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [2.5, 0.3]
_ = analit.grad(x)
H_analit = analit.hess(x)
ngev += 1
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
_ = approx.grad(x)
H_approx = approx.hess(x)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v))
ngev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [5.2, 2.3]
_ = analit.grad(x)
H_analit = analit.hess(x)
ngev += 1
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
_ = approx.grad(x)
H_approx = approx.hess(x)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v))
ngev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.ngev, ngev)
assert_array_equal(analit.ngev+approx.ngev, ngev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
class ExVectorialFunction:
def __init__(self):
self.nfev = 0
self.njev = 0
self.nhev = 0
def fun(self, x):
self.nfev += 1
return np.array([2*(x[0]**2 + x[1]**2 - 1) - x[0],
4*(x[0]**3 + x[1]**2 - 4) - 3*x[0]])
def jac(self, x):
self.njev += 1
return np.array([[4*x[0]-1, 4*x[1]],
[12*x[0]**2-3, 8*x[1]]])
def hess(self, x, v):
self.nhev += 1
return v[0]*4*np.eye(2) + v[1]*np.array([[24*x[0], 0],
[0, 8]])
class TestVectorialFunction(TestCase):
def test_finite_difference_jac(self):
ex = ExVectorialFunction()
nfev = 0
njev = 0
x0 = [1.0, 0.0]
v0 = [0.0, 1.0]
analit = VectorFunction(ex.fun, x0, ex.jac, ex.hess, None, None,
(-np.inf, np.inf), None)
nfev += 1
njev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev, njev)
approx = VectorFunction(ex.fun, x0, '2-point', ex.hess, None, None,
(-np.inf, np.inf), None)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(analit.f, approx.f)
assert_array_almost_equal(analit.J, approx.J)
x = [10, 0.3]
f_analit = analit.fun(x)
J_analit = analit.jac(x)
nfev += 1
njev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
f_approx = approx.fun(x)
J_approx = approx.jac(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_almost_equal(f_analit, f_approx)
assert_array_almost_equal(J_analit, J_approx, decimal=4)
x = [2.0, 1.0]
J_analit = analit.jac(x)
njev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
J_approx = approx.jac(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_almost_equal(J_analit, J_approx)
x = [2.5, 0.3]
f_analit = analit.fun(x)
J_analit = analit.jac(x)
nfev += 1
njev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
f_approx = approx.fun(x)
J_approx = approx.jac(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_almost_equal(f_analit, f_approx)
assert_array_almost_equal(J_analit, J_approx)
x = [2, 0.3]
f_analit = analit.fun(x)
J_analit = analit.jac(x)
nfev += 1
njev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
f_approx = approx.fun(x)
J_approx = approx.jac(x)
nfev += 3
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_almost_equal(f_analit, f_approx)
assert_array_almost_equal(J_analit, J_approx)
def test_finite_difference_hess_linear_operator(self):
ex = ExVectorialFunction()
nfev = 0
njev = 0
nhev = 0
x0 = [1.0, 0.0]
v0 = [1.0, 2.0]
analit = VectorFunction(ex.fun, x0, ex.jac, ex.hess, None, None,
(-np.inf, np.inf), None)
nfev += 1
njev += 1
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev, nhev)
approx = VectorFunction(ex.fun, x0, ex.jac, '2-point', None, None,
(-np.inf, np.inf), None)
assert_(isinstance(approx.H, LinearOperator))
for p in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_equal(analit.f, approx.f)
assert_array_almost_equal(analit.J, approx.J)
assert_array_almost_equal(analit.H.dot(p), approx.H.dot(p))
nfev += 1
njev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [2.0, 1.0]
H_analit = analit.hess(x, v0)
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
H_approx = approx.hess(x, v0)
assert_(isinstance(H_approx, LinearOperator))
for p in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(p), H_approx.dot(p),
decimal=5)
njev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [2.1, 1.2]
v = [1.0, 1.0]
H_analit = analit.hess(x, v)
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
H_approx = approx.hess(x, v)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v))
njev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [2.5, 0.3]
_ = analit.jac(x)
H_analit = analit.hess(x, v0)
njev += 1
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
_ = approx.jac(x)
H_approx = approx.hess(x, v0)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v), decimal=4)
njev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
x = [5.2, 2.3]
v = [2.3, 5.2]
_ = analit.jac(x)
H_analit = analit.hess(x, v)
njev += 1
nhev += 1
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
_ = approx.jac(x)
H_approx = approx.hess(x, v)
assert_(isinstance(H_approx, LinearOperator))
for v in ([1.0, 2.0], [3.0, 4.0], [5.0, 2.0]):
assert_array_almost_equal(H_analit.dot(v), H_approx.dot(v), decimal=4)
njev += 4
assert_array_equal(ex.nfev, nfev)
assert_array_equal(analit.nfev+approx.nfev, nfev)
assert_array_equal(ex.njev, njev)
assert_array_equal(analit.njev+approx.njev, njev)
assert_array_equal(ex.nhev, nhev)
assert_array_equal(analit.nhev+approx.nhev, nhev)
def test_LinearVectorFunction():
A_dense = np.array([
[-1, 2, 0],
[0, 4, 2]
])
x0 = np.zeros(3)
A_sparse = csr_matrix(A_dense)
x = np.array([1, -1, 0])
v = np.array([-1, 1])
Ax = np.array([-3, -4])
f1 = LinearVectorFunction(A_dense, x0, None)
assert_(not f1.sparse_jacobian)
f2 = LinearVectorFunction(A_dense, x0, True)
assert_(f2.sparse_jacobian)
f3 = LinearVectorFunction(A_dense, x0, False)
assert_(not f3.sparse_jacobian)
f4 = LinearVectorFunction(A_sparse, x0, None)
assert_(f4.sparse_jacobian)
f5 = LinearVectorFunction(A_sparse, x0, True)
assert_(f5.sparse_jacobian)
f6 = LinearVectorFunction(A_sparse, x0, False)
assert_(not f6.sparse_jacobian)
assert_array_equal(f1.fun(x), Ax)
assert_array_equal(f2.fun(x), Ax)
assert_array_equal(f1.jac(x), A_dense)
assert_array_equal(f2.jac(x).toarray(), A_sparse.toarray())
assert_array_equal(f1.hess(x, v).toarray(), np.zeros((3, 3)))
def test_LinearVectorFunction_memoization():
A = np.array([[-1, 2, 0], [0, 4, 2]])
x0 = np.array([1, 2, -1])
fun = LinearVectorFunction(A, x0, False)
assert_array_equal(x0, fun.x)
assert_array_equal(A.dot(x0), fun.f)
x1 = np.array([-1, 3, 10])
assert_array_equal(A, fun.jac(x1))
assert_array_equal(x1, fun.x)
assert_array_equal(A.dot(x0), fun.f)
assert_array_equal(A.dot(x1), fun.fun(x1))
assert_array_equal(A.dot(x1), fun.f)
def test_IdentityVectorFunction():
x0 = np.zeros(3)
f1 = IdentityVectorFunction(x0, None)
f2 = IdentityVectorFunction(x0, False)
f3 = IdentityVectorFunction(x0, True)
assert_(f1.sparse_jacobian)
assert_(not f2.sparse_jacobian)
assert_(f3.sparse_jacobian)
x = np.array([-1, 2, 1])
v = np.array([-2, 3, 0])
assert_array_equal(f1.fun(x), x)
assert_array_equal(f2.fun(x), x)
assert_array_equal(f1.jac(x).toarray(), np.eye(3))
assert_array_equal(f2.jac(x), np.eye(3))
assert_array_equal(f1.hess(x, v).toarray(), np.zeros((3, 3)))
@@ -0,0 +1,219 @@
from __future__ import division, print_function, absolute_import
import numpy as np
from copy import deepcopy
from numpy.linalg import norm
from numpy.testing import (TestCase, assert_array_almost_equal,
assert_array_equal, assert_array_less,
assert_raises, assert_equal, assert_,
run_module_suite, assert_allclose, assert_warns,
dec)
from scipy.optimize import (BFGS,
SR1,
HessianUpdateStrategy,
minimize)
class Rosenbrock:
"""Rosenbrock function.
The following optimization problem:
minimize sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
"""
def __init__(self, n=2, random_state=0):
rng = np.random.RandomState(random_state)
self.x0 = rng.uniform(-1, 1, n)
self.x_opt = np.ones(n)
def fun(self, x):
x = np.asarray(x)
r = np.sum(100.0 * (x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0,
axis=0)
return r
def grad(self, x):
x = np.asarray(x)
xm = x[1:-1]
xm_m1 = x[:-2]
xm_p1 = x[2:]
der = np.zeros_like(x)
der[1:-1] = (200 * (xm - xm_m1**2) -
400 * (xm_p1 - xm**2) * xm - 2 * (1 - xm))
der[0] = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
der[-1] = 200 * (x[-1] - x[-2]**2)
return der
def hess(self, x):
x = np.atleast_1d(x)
H = np.diag(-400 * x[:-1], 1) - np.diag(400 * x[:-1], -1)
diagonal = np.zeros(len(x), dtype=x.dtype)
diagonal[0] = 1200 * x[0]**2 - 400 * x[1] + 2
diagonal[-1] = 200
diagonal[1:-1] = 202 + 1200 * x[1:-1]**2 - 400 * x[2:]
H = H + np.diag(diagonal)
return H
class TestHessianUpdateStrategy(TestCase):
def test_hessian_initialization(self):
quasi_newton = (BFGS(), SR1())
for qn in quasi_newton:
qn.initialize(5, 'hess')
B = qn.get_matrix()
assert_array_equal(B, np.eye(5))
# For this list of points it is known
# that no exception occur during the
# Hessian update. Hence no update is
# skiped or damped.
def test_rosenbrock_with_no_exception(self):
# Define auxiliar problem
prob = Rosenbrock(n=5)
# Define iteration points
x_list = [[0.0976270, 0.4303787, 0.2055267, 0.0897663, -0.15269040],
[0.1847239, 0.0505757, 0.2123832, 0.0255081, 0.00083286],
[0.2142498, -0.0188480, 0.0503822, 0.0347033, 0.03323606],
[0.2071680, -0.0185071, 0.0341337, -0.0139298, 0.02881750],
[0.1533055, -0.0322935, 0.0280418, -0.0083592, 0.01503699],
[0.1382378, -0.0276671, 0.0266161, -0.0074060, 0.02801610],
[0.1651957, -0.0049124, 0.0269665, -0.0040025, 0.02138184],
[0.2354930, 0.0443711, 0.0173959, 0.0041872, 0.00794563],
[0.4168118, 0.1433867, 0.0111714, 0.0126265, -0.00658537],
[0.4681972, 0.2153273, 0.0225249, 0.0152704, -0.00463809],
[0.6023068, 0.3346815, 0.0731108, 0.0186618, -0.00371541],
[0.6415743, 0.3985468, 0.1324422, 0.0214160, -0.00062401],
[0.7503690, 0.5447616, 0.2804541, 0.0539851, 0.00242230],
[0.7452626, 0.5644594, 0.3324679, 0.0865153, 0.00454960],
[0.8059782, 0.6586838, 0.4229577, 0.1452990, 0.00976702],
[0.8549542, 0.7226562, 0.4991309, 0.2420093, 0.02772661],
[0.8571332, 0.7285741, 0.5279076, 0.2824549, 0.06030276],
[0.8835633, 0.7727077, 0.5957984, 0.3411303, 0.09652185],
[0.9071558, 0.8299587, 0.6771400, 0.4402896, 0.17469338],
[0.9190793, 0.8486480, 0.7163332, 0.5083780, 0.26107691],
[0.9371223, 0.8762177, 0.7653702, 0.5773109, 0.32181041],
[0.9554613, 0.9119893, 0.8282687, 0.6776178, 0.43162744],
[0.9545744, 0.9099264, 0.8270244, 0.6822220, 0.45237623],
[0.9688112, 0.9351710, 0.8730961, 0.7546601, 0.56622448],
[0.9743227, 0.9491953, 0.9005150, 0.8086497, 0.64505437],
[0.9807345, 0.9638853, 0.9283012, 0.8631675, 0.73812581],
[0.9886746, 0.9777760, 0.9558950, 0.9123417, 0.82726553],
[0.9899096, 0.9803828, 0.9615592, 0.9255600, 0.85822149],
[0.9969510, 0.9935441, 0.9864657, 0.9726775, 0.94358663],
[0.9979533, 0.9960274, 0.9921724, 0.9837415, 0.96626288],
[0.9995981, 0.9989171, 0.9974178, 0.9949954, 0.99023356],
[1.0002640, 1.0005088, 1.0010594, 1.0021161, 1.00386912],
[0.9998903, 0.9998459, 0.9997795, 0.9995484, 0.99916305],
[1.0000008, 0.9999905, 0.9999481, 0.9998903, 0.99978047],
[1.0000004, 0.9999983, 1.0000001, 1.0000031, 1.00000297],
[0.9999995, 1.0000003, 1.0000005, 1.0000001, 1.00000032],
[0.9999999, 0.9999997, 0.9999994, 0.9999989, 0.99999786],
[0.9999999, 0.9999999, 0.9999999, 0.9999999, 0.99999991]]
# Get iteration points
grad_list = [prob.grad(x) for x in x_list]
delta_x = [np.array(x_list[i+1])-np.array(x_list[i])
for i in range(len(x_list)-1)]
delta_grad = [grad_list[i+1]-grad_list[i]
for i in range(len(grad_list)-1)]
# Check curvature condition
for i in range(len(delta_x)):
s = delta_x[i]
y = delta_grad[i]
if np.dot(s, y) <= 0:
raise ArithmeticError()
# Define QuasiNewton update
for quasi_newton in (BFGS(init_scale=1, min_curvature=1e-4),
SR1(init_scale=1)):
hess = deepcopy(quasi_newton)
inv_hess = deepcopy(quasi_newton)
hess.initialize(len(x_list[0]), 'hess')
inv_hess.initialize(len(x_list[0]), 'inv_hess')
# Compare the hessian and its inverse
for i in range(len(delta_x)):
s = delta_x[i]
y = delta_grad[i]
hess.update(s, y)
inv_hess.update(s, y)
B = hess.get_matrix()
H = inv_hess.get_matrix()
assert_array_almost_equal(np.linalg.inv(B), H, decimal=10)
B_true = prob.hess(x_list[i+1])
assert_array_less(norm(B - B_true)/norm(B_true), 0.1)
def test_SR1_skip_update(self):
# Define auxiliar problem
prob = Rosenbrock(n=5)
# Define iteration points
x_list = [[0.0976270, 0.4303787, 0.2055267, 0.0897663, -0.15269040],
[0.1847239, 0.0505757, 0.2123832, 0.0255081, 0.00083286],
[0.2142498, -0.0188480, 0.0503822, 0.0347033, 0.03323606],
[0.2071680, -0.0185071, 0.0341337, -0.0139298, 0.02881750],
[0.1533055, -0.0322935, 0.0280418, -0.0083592, 0.01503699],
[0.1382378, -0.0276671, 0.0266161, -0.0074060, 0.02801610],
[0.1651957, -0.0049124, 0.0269665, -0.0040025, 0.02138184],
[0.2354930, 0.0443711, 0.0173959, 0.0041872, 0.00794563],
[0.4168118, 0.1433867, 0.0111714, 0.0126265, -0.00658537],
[0.4681972, 0.2153273, 0.0225249, 0.0152704, -0.00463809],
[0.6023068, 0.3346815, 0.0731108, 0.0186618, -0.00371541],
[0.6415743, 0.3985468, 0.1324422, 0.0214160, -0.00062401],
[0.7503690, 0.5447616, 0.2804541, 0.0539851, 0.00242230],
[0.7452626, 0.5644594, 0.3324679, 0.0865153, 0.00454960],
[0.8059782, 0.6586838, 0.4229577, 0.1452990, 0.00976702],
[0.8549542, 0.7226562, 0.4991309, 0.2420093, 0.02772661],
[0.8571332, 0.7285741, 0.5279076, 0.2824549, 0.06030276],
[0.8835633, 0.7727077, 0.5957984, 0.3411303, 0.09652185],
[0.9071558, 0.8299587, 0.6771400, 0.4402896, 0.17469338]]
# Get iteration points
grad_list = [prob.grad(x) for x in x_list]
delta_x = [np.array(x_list[i+1])-np.array(x_list[i])
for i in range(len(x_list)-1)]
delta_grad = [grad_list[i+1]-grad_list[i]
for i in range(len(grad_list)-1)]
hess = SR1(init_scale=1, min_denominator=1e-2)
hess.initialize(len(x_list[0]), 'hess')
# Compare the hessian and its inverse
for i in range(len(delta_x)-1):
s = delta_x[i]
y = delta_grad[i]
hess.update(s, y)
# Test skip update
B = np.copy(hess.get_matrix())
s = delta_x[17]
y = delta_grad[17]
hess.update(s, y)
B_updated = np.copy(hess.get_matrix())
assert_array_equal(B, B_updated)
def test_BFGS_skip_update(self):
# Define auxiliar problem
prob = Rosenbrock(n=5)
# Define iteration points
x_list = [[0.0976270, 0.4303787, 0.2055267, 0.0897663, -0.15269040],
[0.1847239, 0.0505757, 0.2123832, 0.0255081, 0.00083286],
[0.2142498, -0.0188480, 0.0503822, 0.0347033, 0.03323606],
[0.2071680, -0.0185071, 0.0341337, -0.0139298, 0.02881750],
[0.1533055, -0.0322935, 0.0280418, -0.0083592, 0.01503699],
[0.1382378, -0.0276671, 0.0266161, -0.0074060, 0.02801610],
[0.1651957, -0.0049124, 0.0269665, -0.0040025, 0.02138184]]
# Get iteration points
grad_list = [prob.grad(x) for x in x_list]
delta_x = [np.array(x_list[i+1])-np.array(x_list[i])
for i in range(len(x_list)-1)]
delta_grad = [grad_list[i+1]-grad_list[i]
for i in range(len(grad_list)-1)]
hess = BFGS(init_scale=1, min_curvature=10)
hess.initialize(len(x_list[0]), 'hess')
# Compare the hessian and its inverse
for i in range(len(delta_x)-1):
s = delta_x[i]
y = delta_grad[i]
hess.update(s, y)
# Test skip update
B = np.copy(hess.get_matrix())
s = delta_x[5]
y = delta_grad[5]
hess.update(s, y)
B_updated = np.copy(hess.get_matrix())
assert_array_equal(B, B_updated)
@@ -0,0 +1,74 @@
# Author: Brian M. Clapper, G. Varoquaux, Lars Buitinck
# License: BSD
from numpy.testing import assert_array_equal
from pytest import raises as assert_raises
import numpy as np
from scipy.optimize import linear_sum_assignment
def test_linear_sum_assignment():
for cost_matrix, expected_cost in [
# Square
([[400, 150, 400],
[400, 450, 600],
[300, 225, 300]],
[150, 400, 300]
),
# Rectangular variant
([[400, 150, 400, 1],
[400, 450, 600, 2],
[300, 225, 300, 3]],
[150, 2, 300]),
# Square
([[10, 10, 8],
[9, 8, 1],
[9, 7, 4]],
[10, 1, 7]),
# Rectangular variant
([[10, 10, 8, 11],
[9, 8, 1, 1],
[9, 7, 4, 10]],
[10, 1, 4]),
# n == 2, m == 0 matrix
([[], []],
[]),
]:
cost_matrix = np.array(cost_matrix)
row_ind, col_ind = linear_sum_assignment(cost_matrix)
assert_array_equal(row_ind, np.sort(row_ind))
assert_array_equal(expected_cost, cost_matrix[row_ind, col_ind])
cost_matrix = cost_matrix.T
row_ind, col_ind = linear_sum_assignment(cost_matrix)
assert_array_equal(row_ind, np.sort(row_ind))
assert_array_equal(np.sort(expected_cost),
np.sort(cost_matrix[row_ind, col_ind]))
def test_linear_sum_assignment_input_validation():
assert_raises(ValueError, linear_sum_assignment, [1, 2, 3])
C = [[1, 2, 3], [4, 5, 6]]
assert_array_equal(linear_sum_assignment(C),
linear_sum_assignment(np.asarray(C)))
assert_array_equal(linear_sum_assignment(C),
linear_sum_assignment(np.matrix(C)))
I = np.identity(3)
assert_array_equal(linear_sum_assignment(I.astype(np.bool)),
linear_sum_assignment(I))
assert_raises(ValueError, linear_sum_assignment, I.astype(str))
I[0][0] = np.nan
assert_raises(ValueError, linear_sum_assignment, I)
I = np.identity(3)
I[1][1] = np.inf
assert_raises(ValueError, linear_sum_assignment, I)
@@ -0,0 +1,45 @@
from __future__ import division, print_function, absolute_import
import numpy as np
from numpy.testing import assert_, assert_allclose
import scipy.linalg
from scipy.optimize import minimize
def test_1():
def f(x):
return x**4, 4*x**3
for gtol in [1e-8, 1e-12, 1e-20]:
for maxcor in range(20, 35):
result = minimize(fun=f, jac=True, method='L-BFGS-B', x0=20,
options={'gtol': gtol, 'maxcor': maxcor})
H1 = result.hess_inv(np.array([1])).reshape(1,1)
H2 = result.hess_inv.todense()
assert_allclose(H1, H2)
def test_2():
H0 = [[3, 0], [1, 2]]
def f(x):
return np.dot(x, np.dot(scipy.linalg.inv(H0), x))
result1 = minimize(fun=f, method='L-BFGS-B', x0=[10, 20])
result2 = minimize(fun=f, method='BFGS', x0=[10, 20])
H1 = result1.hess_inv.todense()
H2 = np.vstack((
result1.hess_inv(np.array([1, 0])),
result1.hess_inv(np.array([0, 1]))))
assert_allclose(
result1.hess_inv(np.array([1, 0]).reshape(2,1)).reshape(-1),
result1.hess_inv(np.array([1, 0])))
assert_allclose(H1, H2)
assert_allclose(H1, result2.hess_inv, rtol=1e-2, atol=0.03)
@@ -0,0 +1,735 @@
from __future__ import division
from itertools import product
import numpy as np
from numpy.linalg import norm
from numpy.testing import (assert_, assert_allclose,
assert_equal)
from pytest import raises as assert_raises
from scipy._lib._numpy_compat import suppress_warnings
from scipy.sparse import issparse, lil_matrix
from scipy.sparse.linalg import aslinearoperator
from scipy.optimize import least_squares
from scipy.optimize._lsq.least_squares import IMPLEMENTED_LOSSES
from scipy.optimize._lsq.common import EPS, make_strictly_feasible
def fun_trivial(x, a=0):
return (x - a)**2 + 5.0
def jac_trivial(x, a=0.0):
return 2 * (x - a)
def fun_2d_trivial(x):
return np.array([x[0], x[1]])
def jac_2d_trivial(x):
return np.identity(2)
def fun_rosenbrock(x):
return np.array([10 * (x[1] - x[0]**2), (1 - x[0])])
def jac_rosenbrock(x):
return np.array([
[-20 * x[0], 10],
[-1, 0]
])
def jac_rosenbrock_bad_dim(x):
return np.array([
[-20 * x[0], 10],
[-1, 0],
[0.0, 0.0]
])
def fun_rosenbrock_cropped(x):
return fun_rosenbrock(x)[0]
def jac_rosenbrock_cropped(x):
return jac_rosenbrock(x)[0]
# When x is 1-d array, return is 2-d array.
def fun_wrong_dimensions(x):
return np.array([x, x**2, x**3])
def jac_wrong_dimensions(x, a=0.0):
return np.atleast_3d(jac_trivial(x, a=a))
def fun_bvp(x):
n = int(np.sqrt(x.shape[0]))
u = np.zeros((n + 2, n + 2))
x = x.reshape((n, n))
u[1:-1, 1:-1] = x
y = u[:-2, 1:-1] + u[2:, 1:-1] + u[1:-1, :-2] + u[1:-1, 2:] - 4 * x + x**3
return y.ravel()
class BroydenTridiagonal(object):
def __init__(self, n=100, mode='sparse'):
np.random.seed(0)
self.n = n
self.x0 = -np.ones(n)
self.lb = np.linspace(-2, -1.5, n)
self.ub = np.linspace(-0.8, 0.0, n)
self.lb += 0.1 * np.random.randn(n)
self.ub += 0.1 * np.random.randn(n)
self.x0 += 0.1 * np.random.randn(n)
self.x0 = make_strictly_feasible(self.x0, self.lb, self.ub)
if mode == 'sparse':
self.sparsity = lil_matrix((n, n), dtype=int)
i = np.arange(n)
self.sparsity[i, i] = 1
i = np.arange(1, n)
self.sparsity[i, i - 1] = 1
i = np.arange(n - 1)
self.sparsity[i, i + 1] = 1
self.jac = self._jac
elif mode == 'operator':
self.jac = lambda x: aslinearoperator(self._jac(x))
elif mode == 'dense':
self.sparsity = None
self.jac = lambda x: self._jac(x).toarray()
else:
assert_(False)
def fun(self, x):
f = (3 - x) * x + 1
f[1:] -= x[:-1]
f[:-1] -= 2 * x[1:]
return f
def _jac(self, x):
J = lil_matrix((self.n, self.n))
i = np.arange(self.n)
J[i, i] = 3 - 2 * x
i = np.arange(1, self.n)
J[i, i - 1] = -1
i = np.arange(self.n - 1)
J[i, i + 1] = -2
return J
class ExponentialFittingProblem(object):
"""Provide data and function for exponential fitting in the form
y = a + exp(b * x) + noise."""
def __init__(self, a, b, noise, n_outliers=1, x_range=(-1, 1),
n_points=11, random_seed=None):
np.random.seed(random_seed)
self.m = n_points
self.n = 2
self.p0 = np.zeros(2)
self.x = np.linspace(x_range[0], x_range[1], n_points)
self.y = a + np.exp(b * self.x)
self.y += noise * np.random.randn(self.m)
outliers = np.random.randint(0, self.m, n_outliers)
self.y[outliers] += 50 * noise * np.random.rand(n_outliers)
self.p_opt = np.array([a, b])
def fun(self, p):
return p[0] + np.exp(p[1] * self.x) - self.y
def jac(self, p):
J = np.empty((self.m, self.n))
J[:, 0] = 1
J[:, 1] = self.x * np.exp(p[1] * self.x)
return J
def cubic_soft_l1(z):
rho = np.empty((3, z.size))
t = 1 + z
rho[0] = 3 * (t**(1/3) - 1)
rho[1] = t ** (-2/3)
rho[2] = -2/3 * t**(-5/3)
return rho
LOSSES = list(IMPLEMENTED_LOSSES.keys()) + [cubic_soft_l1]
class BaseMixin(object):
def test_basic(self):
# Test that the basic calling sequence works.
res = least_squares(fun_trivial, 2., method=self.method)
assert_allclose(res.x, 0, atol=1e-4)
assert_allclose(res.fun, fun_trivial(res.x))
def test_args_kwargs(self):
# Test that args and kwargs are passed correctly to the functions.
a = 3.0
for jac in ['2-point', '3-point', 'cs', jac_trivial]:
with suppress_warnings() as sup:
sup.filter(UserWarning,
"jac='(3-point|cs)' works equivalently to '2-point' for method='lm'")
res = least_squares(fun_trivial, 2.0, jac, args=(a,),
method=self.method)
res1 = least_squares(fun_trivial, 2.0, jac, kwargs={'a': a},
method=self.method)
assert_allclose(res.x, a, rtol=1e-4)
assert_allclose(res1.x, a, rtol=1e-4)
assert_raises(TypeError, least_squares, fun_trivial, 2.0,
args=(3, 4,), method=self.method)
assert_raises(TypeError, least_squares, fun_trivial, 2.0,
kwargs={'kaboom': 3}, method=self.method)
def test_jac_options(self):
for jac in ['2-point', '3-point', 'cs', jac_trivial]:
with suppress_warnings() as sup:
sup.filter(UserWarning,
"jac='(3-point|cs)' works equivalently to '2-point' for method='lm'")
res = least_squares(fun_trivial, 2.0, jac, method=self.method)
assert_allclose(res.x, 0, atol=1e-4)
assert_raises(ValueError, least_squares, fun_trivial, 2.0, jac='oops',
method=self.method)
def test_nfev_options(self):
for max_nfev in [None, 20]:
res = least_squares(fun_trivial, 2.0, max_nfev=max_nfev,
method=self.method)
assert_allclose(res.x, 0, atol=1e-4)
def test_x_scale_options(self):
for x_scale in [1.0, np.array([0.5]), 'jac']:
res = least_squares(fun_trivial, 2.0, x_scale=x_scale)
assert_allclose(res.x, 0)
assert_raises(ValueError, least_squares, fun_trivial,
2.0, x_scale='auto', method=self.method)
assert_raises(ValueError, least_squares, fun_trivial,
2.0, x_scale=-1.0, method=self.method)
assert_raises(ValueError, least_squares, fun_trivial,
2.0, x_scale=None, method=self.method)
assert_raises(ValueError, least_squares, fun_trivial,
2.0, x_scale=1.0+2.0j, method=self.method)
def test_diff_step(self):
# res1 and res2 should be equivalent.
# res2 and res3 should be different.
res1 = least_squares(fun_trivial, 2.0, diff_step=1e-1,
method=self.method)
res2 = least_squares(fun_trivial, 2.0, diff_step=-1e-1,
method=self.method)
res3 = least_squares(fun_trivial, 2.0,
diff_step=None, method=self.method)
assert_allclose(res1.x, 0, atol=1e-4)
assert_allclose(res2.x, 0, atol=1e-4)
assert_allclose(res3.x, 0, atol=1e-4)
assert_equal(res1.x, res2.x)
assert_equal(res1.nfev, res2.nfev)
assert_(res2.nfev != res3.nfev)
def test_incorrect_options_usage(self):
assert_raises(TypeError, least_squares, fun_trivial, 2.0,
method=self.method, options={'no_such_option': 100})
assert_raises(TypeError, least_squares, fun_trivial, 2.0,
method=self.method, options={'max_nfev': 100})
def test_full_result(self):
# MINPACK doesn't work very well with factor=100 on this problem,
# thus using low 'atol'.
res = least_squares(fun_trivial, 2.0, method=self.method)
assert_allclose(res.x, 0, atol=1e-4)
assert_allclose(res.cost, 12.5)
assert_allclose(res.fun, 5)
assert_allclose(res.jac, 0, atol=1e-4)
assert_allclose(res.grad, 0, atol=1e-2)
assert_allclose(res.optimality, 0, atol=1e-2)
assert_equal(res.active_mask, 0)
if self.method == 'lm':
assert_(res.nfev < 30)
assert_(res.njev is None)
else:
assert_(res.nfev < 10)
assert_(res.njev < 10)
assert_(res.status > 0)
assert_(res.success)
def test_full_result_single_fev(self):
# MINPACK checks the number of nfev after the iteration,
# so it's hard to tell what he is going to compute.
if self.method == 'lm':
return
res = least_squares(fun_trivial, 2.0, method=self.method,
max_nfev=1)
assert_equal(res.x, np.array([2]))
assert_equal(res.cost, 40.5)
assert_equal(res.fun, np.array([9]))
assert_equal(res.jac, np.array([[4]]))
assert_equal(res.grad, np.array([36]))
assert_equal(res.optimality, 36)
assert_equal(res.active_mask, np.array([0]))
assert_equal(res.nfev, 1)
assert_equal(res.njev, 1)
assert_equal(res.status, 0)
assert_equal(res.success, 0)
def test_rosenbrock(self):
x0 = [-2, 1]
x_opt = [1, 1]
for jac, x_scale, tr_solver in product(
['2-point', '3-point', 'cs', jac_rosenbrock],
[1.0, np.array([1.0, 0.2]), 'jac'],
['exact', 'lsmr']):
with suppress_warnings() as sup:
sup.filter(UserWarning,
"jac='(3-point|cs)' works equivalently to '2-point' for method='lm'")
res = least_squares(fun_rosenbrock, x0, jac, x_scale=x_scale,
tr_solver=tr_solver, method=self.method)
assert_allclose(res.x, x_opt)
def test_rosenbrock_cropped(self):
x0 = [-2, 1]
if self.method == 'lm':
assert_raises(ValueError, least_squares, fun_rosenbrock_cropped,
x0, method='lm')
else:
for jac, x_scale, tr_solver in product(
['2-point', '3-point', 'cs', jac_rosenbrock_cropped],
[1.0, np.array([1.0, 0.2]), 'jac'],
['exact', 'lsmr']):
res = least_squares(
fun_rosenbrock_cropped, x0, jac, x_scale=x_scale,
tr_solver=tr_solver, method=self.method)
assert_allclose(res.cost, 0, atol=1e-14)
def test_fun_wrong_dimensions(self):
assert_raises(ValueError, least_squares, fun_wrong_dimensions,
2.0, method=self.method)
def test_jac_wrong_dimensions(self):
assert_raises(ValueError, least_squares, fun_trivial,
2.0, jac_wrong_dimensions, method=self.method)
def test_fun_and_jac_inconsistent_dimensions(self):
x0 = [1, 2]
assert_raises(ValueError, least_squares, fun_rosenbrock, x0,
jac_rosenbrock_bad_dim, method=self.method)
def test_x0_multidimensional(self):
x0 = np.ones(4).reshape(2, 2)
assert_raises(ValueError, least_squares, fun_trivial, x0,
method=self.method)
def test_x0_complex_scalar(self):
x0 = 2.0 + 0.0*1j
assert_raises(ValueError, least_squares, fun_trivial, x0,
method=self.method)
def test_x0_complex_array(self):
x0 = [1.0, 2.0 + 0.0*1j]
assert_raises(ValueError, least_squares, fun_trivial, x0,
method=self.method)
def test_bvp(self):
# This test was introduced with fix #5556. It turned out that
# dogbox solver had a bug with trust-region radius update, which
# could block its progress and create an infinite loop. And this
# discrete boundary value problem is the one which triggers it.
n = 10
x0 = np.ones(n**2)
if self.method == 'lm':
max_nfev = 5000 # To account for Jacobian estimation.
else:
max_nfev = 100
res = least_squares(fun_bvp, x0, ftol=1e-2, method=self.method,
max_nfev=max_nfev)
assert_(res.nfev < max_nfev)
assert_(res.cost < 0.5)
class BoundsMixin(object):
def test_inconsistent(self):
assert_raises(ValueError, least_squares, fun_trivial, 2.0,
bounds=(10.0, 0.0), method=self.method)
def test_infeasible(self):
assert_raises(ValueError, least_squares, fun_trivial, 2.0,
bounds=(3., 4), method=self.method)
def test_wrong_number(self):
assert_raises(ValueError, least_squares, fun_trivial, 2.,
bounds=(1., 2, 3), method=self.method)
def test_inconsistent_shape(self):
assert_raises(ValueError, least_squares, fun_trivial, 2.0,
bounds=(1.0, [2.0, 3.0]), method=self.method)
# 1-D array wont't be broadcasted
assert_raises(ValueError, least_squares, fun_rosenbrock, [1.0, 2.0],
bounds=([0.0], [3.0, 4.0]), method=self.method)
def test_in_bounds(self):
for jac in ['2-point', '3-point', 'cs', jac_trivial]:
res = least_squares(fun_trivial, 2.0, jac=jac,
bounds=(-1.0, 3.0), method=self.method)
assert_allclose(res.x, 0.0, atol=1e-4)
assert_equal(res.active_mask, [0])
assert_(-1 <= res.x <= 3)
res = least_squares(fun_trivial, 2.0, jac=jac,
bounds=(0.5, 3.0), method=self.method)
assert_allclose(res.x, 0.5, atol=1e-4)
assert_equal(res.active_mask, [-1])
assert_(0.5 <= res.x <= 3)
def test_bounds_shape(self):
for jac in ['2-point', '3-point', 'cs', jac_2d_trivial]:
x0 = [1.0, 1.0]
res = least_squares(fun_2d_trivial, x0, jac=jac)
assert_allclose(res.x, [0.0, 0.0])
res = least_squares(fun_2d_trivial, x0, jac=jac,
bounds=(0.5, [2.0, 2.0]), method=self.method)
assert_allclose(res.x, [0.5, 0.5])
res = least_squares(fun_2d_trivial, x0, jac=jac,
bounds=([0.3, 0.2], 3.0), method=self.method)
assert_allclose(res.x, [0.3, 0.2])
res = least_squares(
fun_2d_trivial, x0, jac=jac, bounds=([-1, 0.5], [1.0, 3.0]),
method=self.method)
assert_allclose(res.x, [0.0, 0.5], atol=1e-5)
def test_rosenbrock_bounds(self):
x0_1 = np.array([-2.0, 1.0])
x0_2 = np.array([2.0, 2.0])
x0_3 = np.array([-2.0, 2.0])
x0_4 = np.array([0.0, 2.0])
x0_5 = np.array([-1.2, 1.0])
problems = [
(x0_1, ([-np.inf, -1.5], np.inf)),
(x0_2, ([-np.inf, 1.5], np.inf)),
(x0_3, ([-np.inf, 1.5], np.inf)),
(x0_4, ([-np.inf, 1.5], [1.0, np.inf])),
(x0_2, ([1.0, 1.5], [3.0, 3.0])),
(x0_5, ([-50.0, 0.0], [0.5, 100]))
]
for x0, bounds in problems:
for jac, x_scale, tr_solver in product(
['2-point', '3-point', 'cs', jac_rosenbrock],
[1.0, [1.0, 0.5], 'jac'],
['exact', 'lsmr']):
res = least_squares(fun_rosenbrock, x0, jac, bounds,
x_scale=x_scale, tr_solver=tr_solver,
method=self.method)
assert_allclose(res.optimality, 0.0, atol=1e-5)
class SparseMixin(object):
def test_exact_tr_solver(self):
p = BroydenTridiagonal()
assert_raises(ValueError, least_squares, p.fun, p.x0, p.jac,
tr_solver='exact', method=self.method)
assert_raises(ValueError, least_squares, p.fun, p.x0,
tr_solver='exact', jac_sparsity=p.sparsity,
method=self.method)
def test_equivalence(self):
sparse = BroydenTridiagonal(mode='sparse')
dense = BroydenTridiagonal(mode='dense')
res_sparse = least_squares(
sparse.fun, sparse.x0, jac=sparse.jac,
method=self.method)
res_dense = least_squares(
dense.fun, dense.x0, jac=sparse.jac,
method=self.method)
assert_equal(res_sparse.nfev, res_dense.nfev)
assert_allclose(res_sparse.x, res_dense.x, atol=1e-20)
assert_allclose(res_sparse.cost, 0, atol=1e-20)
assert_allclose(res_dense.cost, 0, atol=1e-20)
def test_tr_options(self):
p = BroydenTridiagonal()
res = least_squares(p.fun, p.x0, p.jac, method=self.method,
tr_options={'btol': 1e-10})
assert_allclose(res.cost, 0, atol=1e-20)
def test_wrong_parameters(self):
p = BroydenTridiagonal()
assert_raises(ValueError, least_squares, p.fun, p.x0, p.jac,
tr_solver='best', method=self.method)
assert_raises(TypeError, least_squares, p.fun, p.x0, p.jac,
tr_solver='lsmr', tr_options={'tol': 1e-10})
def test_solver_selection(self):
sparse = BroydenTridiagonal(mode='sparse')
dense = BroydenTridiagonal(mode='dense')
res_sparse = least_squares(sparse.fun, sparse.x0, jac=sparse.jac,
method=self.method)
res_dense = least_squares(dense.fun, dense.x0, jac=dense.jac,
method=self.method)
assert_allclose(res_sparse.cost, 0, atol=1e-20)
assert_allclose(res_dense.cost, 0, atol=1e-20)
assert_(issparse(res_sparse.jac))
assert_(isinstance(res_dense.jac, np.ndarray))
def test_numerical_jac(self):
p = BroydenTridiagonal()
for jac in ['2-point', '3-point', 'cs']:
res_dense = least_squares(p.fun, p.x0, jac, method=self.method)
res_sparse = least_squares(
p.fun, p.x0, jac,method=self.method,
jac_sparsity=p.sparsity)
assert_equal(res_dense.nfev, res_sparse.nfev)
assert_allclose(res_dense.x, res_sparse.x, atol=1e-20)
assert_allclose(res_dense.cost, 0, atol=1e-20)
assert_allclose(res_sparse.cost, 0, atol=1e-20)
def test_with_bounds(self):
p = BroydenTridiagonal()
for jac, jac_sparsity in product(
[p.jac, '2-point', '3-point', 'cs'], [None, p.sparsity]):
res_1 = least_squares(
p.fun, p.x0, jac, bounds=(p.lb, np.inf),
method=self.method,jac_sparsity=jac_sparsity)
res_2 = least_squares(
p.fun, p.x0, jac, bounds=(-np.inf, p.ub),
method=self.method, jac_sparsity=jac_sparsity)
res_3 = least_squares(
p.fun, p.x0, jac, bounds=(p.lb, p.ub),
method=self.method, jac_sparsity=jac_sparsity)
assert_allclose(res_1.optimality, 0, atol=1e-10)
assert_allclose(res_2.optimality, 0, atol=1e-10)
assert_allclose(res_3.optimality, 0, atol=1e-10)
def test_wrong_jac_sparsity(self):
p = BroydenTridiagonal()
sparsity = p.sparsity[:-1]
assert_raises(ValueError, least_squares, p.fun, p.x0,
jac_sparsity=sparsity, method=self.method)
def test_linear_operator(self):
p = BroydenTridiagonal(mode='operator')
res = least_squares(p.fun, p.x0, p.jac, method=self.method)
assert_allclose(res.cost, 0.0, atol=1e-20)
assert_raises(ValueError, least_squares, p.fun, p.x0, p.jac,
method=self.method, tr_solver='exact')
def test_x_scale_jac_scale(self):
p = BroydenTridiagonal()
res = least_squares(p.fun, p.x0, p.jac, method=self.method,
x_scale='jac')
assert_allclose(res.cost, 0.0, atol=1e-20)
p = BroydenTridiagonal(mode='operator')
assert_raises(ValueError, least_squares, p.fun, p.x0, p.jac,
method=self.method, x_scale='jac')
class LossFunctionMixin(object):
def test_options(self):
for loss in LOSSES:
res = least_squares(fun_trivial, 2.0, loss=loss,
method=self.method)
assert_allclose(res.x, 0, atol=1e-15)
assert_raises(ValueError, least_squares, fun_trivial, 2.0,
loss='hinge', method=self.method)
def test_fun(self):
# Test that res.fun is actual residuals, and not modified by loss
# function stuff.
for loss in LOSSES:
res = least_squares(fun_trivial, 2.0, loss=loss,
method=self.method)
assert_equal(res.fun, fun_trivial(res.x))
def test_grad(self):
# Test that res.grad is true gradient of loss function at the
# solution. Use max_nfev = 1, to avoid reaching minimum.
x = np.array([2.0]) # res.x will be this.
res = least_squares(fun_trivial, x, jac_trivial, loss='linear',
max_nfev=1, method=self.method)
assert_equal(res.grad, 2 * x * (x**2 + 5))
res = least_squares(fun_trivial, x, jac_trivial, loss='huber',
max_nfev=1, method=self.method)
assert_equal(res.grad, 2 * x)
res = least_squares(fun_trivial, x, jac_trivial, loss='soft_l1',
max_nfev=1, method=self.method)
assert_allclose(res.grad,
2 * x * (x**2 + 5) / (1 + (x**2 + 5)**2)**0.5)
res = least_squares(fun_trivial, x, jac_trivial, loss='cauchy',
max_nfev=1, method=self.method)
assert_allclose(res.grad, 2 * x * (x**2 + 5) / (1 + (x**2 + 5)**2))
res = least_squares(fun_trivial, x, jac_trivial, loss='arctan',
max_nfev=1, method=self.method)
assert_allclose(res.grad, 2 * x * (x**2 + 5) / (1 + (x**2 + 5)**4))
res = least_squares(fun_trivial, x, jac_trivial, loss=cubic_soft_l1,
max_nfev=1, method=self.method)
assert_allclose(res.grad,
2 * x * (x**2 + 5) / (1 + (x**2 + 5)**2)**(2/3))
def test_jac(self):
# Test that res.jac.T.dot(res.jac) gives Gauss-Newton approximation
# of Hessian. This approximation is computed by doubly differentiating
# the cost function and dropping the part containing second derivative
# of f. For a scalar function it is computed as
# H = (rho' + 2 * rho'' * f**2) * f'**2, if the expression inside the
# brackets is less than EPS it is replaced by EPS. Here we check
# against the root of H.
x = 2.0 # res.x will be this.
f = x**2 + 5 # res.fun will be this.
res = least_squares(fun_trivial, x, jac_trivial, loss='linear',
max_nfev=1, method=self.method)
assert_equal(res.jac, 2 * x)
# For `huber` loss the Jacobian correction is identically zero
# in outlier region, in such cases it is modified to be equal EPS**0.5.
res = least_squares(fun_trivial, x, jac_trivial, loss='huber',
max_nfev=1, method=self.method)
assert_equal(res.jac, 2 * x * EPS**0.5)
# Now let's apply `loss_scale` to turn the residual into an inlier.
# The loss function becomes linear.
res = least_squares(fun_trivial, x, jac_trivial, loss='huber',
f_scale=10, max_nfev=1)
assert_equal(res.jac, 2 * x)
# 'soft_l1' always gives a positive scaling.
res = least_squares(fun_trivial, x, jac_trivial, loss='soft_l1',
max_nfev=1, method=self.method)
assert_allclose(res.jac, 2 * x * (1 + f**2)**-0.75)
# For 'cauchy' the correction term turns out to be negative, and it
# replaced by EPS**0.5.
res = least_squares(fun_trivial, x, jac_trivial, loss='cauchy',
max_nfev=1, method=self.method)
assert_allclose(res.jac, 2 * x * EPS**0.5)
# Now use scaling to turn the residual to inlier.
res = least_squares(fun_trivial, x, jac_trivial, loss='cauchy',
f_scale=10, max_nfev=1, method=self.method)
fs = f / 10
assert_allclose(res.jac, 2 * x * (1 - fs**2)**0.5 / (1 + fs**2))
# 'arctan' gives an outlier.
res = least_squares(fun_trivial, x, jac_trivial, loss='arctan',
max_nfev=1, method=self.method)
assert_allclose(res.jac, 2 * x * EPS**0.5)
# Turn to inlier.
res = least_squares(fun_trivial, x, jac_trivial, loss='arctan',
f_scale=20.0, max_nfev=1, method=self.method)
fs = f / 20
assert_allclose(res.jac, 2 * x * (1 - 3 * fs**4)**0.5 / (1 + fs**4))
# cubic_soft_l1 will give an outlier.
res = least_squares(fun_trivial, x, jac_trivial, loss=cubic_soft_l1,
max_nfev=1)
assert_allclose(res.jac, 2 * x * EPS**0.5)
# Turn to inlier.
res = least_squares(fun_trivial, x, jac_trivial,
loss=cubic_soft_l1, f_scale=6, max_nfev=1)
fs = f / 6
assert_allclose(res.jac,
2 * x * (1 - fs**2 / 3)**0.5 * (1 + fs**2)**(-5/6))
def test_robustness(self):
for noise in [0.1, 1.0]:
p = ExponentialFittingProblem(1, 0.1, noise, random_seed=0)
for jac in ['2-point', '3-point', 'cs', p.jac]:
res_lsq = least_squares(p.fun, p.p0, jac=jac,
method=self.method)
assert_allclose(res_lsq.optimality, 0, atol=1e-2)
for loss in LOSSES:
if loss == 'linear':
continue
res_robust = least_squares(
p.fun, p.p0, jac=jac, loss=loss, f_scale=noise,
method=self.method)
assert_allclose(res_robust.optimality, 0, atol=1e-2)
assert_(norm(res_robust.x - p.p_opt) <
norm(res_lsq.x - p.p_opt))
class TestDogbox(BaseMixin, BoundsMixin, SparseMixin, LossFunctionMixin):
method = 'dogbox'
class TestTRF(BaseMixin, BoundsMixin, SparseMixin, LossFunctionMixin):
method = 'trf'
def test_lsmr_regularization(self):
p = BroydenTridiagonal()
for regularize in [True, False]:
res = least_squares(p.fun, p.x0, p.jac, method='trf',
tr_options={'regularize': regularize})
assert_allclose(res.cost, 0, atol=1e-20)
class TestLM(BaseMixin):
method = 'lm'
def test_bounds_not_supported(self):
assert_raises(ValueError, least_squares, fun_trivial,
2.0, bounds=(-3.0, 3.0), method='lm')
def test_m_less_n_not_supported(self):
x0 = [-2, 1]
assert_raises(ValueError, least_squares, fun_rosenbrock_cropped, x0,
method='lm')
def test_sparse_not_supported(self):
p = BroydenTridiagonal()
assert_raises(ValueError, least_squares, p.fun, p.x0, p.jac,
method='lm')
def test_jac_sparsity_not_supported(self):
assert_raises(ValueError, least_squares, fun_trivial, 2.0,
jac_sparsity=[1], method='lm')
def test_LinearOperator_not_supported(self):
p = BroydenTridiagonal(mode="operator")
assert_raises(ValueError, least_squares, p.fun, p.x0, p.jac,
method='lm')
def test_loss(self):
res = least_squares(fun_trivial, 2.0, loss='linear', method='lm')
assert_allclose(res.x, 0.0, atol=1e-4)
assert_raises(ValueError, least_squares, fun_trivial, 2.0,
method='lm', loss='huber')
def test_basic():
# test that 'method' arg is really optional
res = least_squares(fun_trivial, 2.0)
assert_allclose(res.x, 0, atol=1e-10)
@@ -0,0 +1,283 @@
"""
Tests for line search routines
"""
from __future__ import division, print_function, absolute_import
from numpy.testing import assert_, assert_equal, \
assert_array_almost_equal, assert_array_almost_equal_nulp, assert_warns
from scipy._lib._numpy_compat import suppress_warnings
import scipy.optimize.linesearch as ls
from scipy.optimize.linesearch import LineSearchWarning
import numpy as np
def assert_wolfe(s, phi, derphi, c1=1e-4, c2=0.9, err_msg=""):
"""
Check that strong Wolfe conditions apply
"""
phi1 = phi(s)
phi0 = phi(0)
derphi0 = derphi(0)
derphi1 = derphi(s)
msg = "s = %s; phi(0) = %s; phi(s) = %s; phi'(0) = %s; phi'(s) = %s; %s" % (
s, phi0, phi1, derphi0, derphi1, err_msg)
assert_(phi1 <= phi0 + c1*s*derphi0, "Wolfe 1 failed: " + msg)
assert_(abs(derphi1) <= abs(c2*derphi0), "Wolfe 2 failed: " + msg)
def assert_armijo(s, phi, c1=1e-4, err_msg=""):
"""
Check that Armijo condition applies
"""
phi1 = phi(s)
phi0 = phi(0)
msg = "s = %s; phi(0) = %s; phi(s) = %s; %s" % (s, phi0, phi1, err_msg)
assert_(phi1 <= (1 - c1*s)*phi0, msg)
def assert_line_wolfe(x, p, s, f, fprime, **kw):
assert_wolfe(s, phi=lambda sp: f(x + p*sp),
derphi=lambda sp: np.dot(fprime(x + p*sp), p), **kw)
def assert_line_armijo(x, p, s, f, **kw):
assert_armijo(s, phi=lambda sp: f(x + p*sp), **kw)
def assert_fp_equal(x, y, err_msg="", nulp=50):
"""Assert two arrays are equal, up to some floating-point rounding error"""
try:
assert_array_almost_equal_nulp(x, y, nulp)
except AssertionError as e:
raise AssertionError("%s\n%s" % (e, err_msg))
class TestLineSearch(object):
# -- scalar functions; must have dphi(0.) < 0
def _scalar_func_1(self, s):
self.fcount += 1
p = -s - s**3 + s**4
dp = -1 - 3*s**2 + 4*s**3
return p, dp
def _scalar_func_2(self, s):
self.fcount += 1
p = np.exp(-4*s) + s**2
dp = -4*np.exp(-4*s) + 2*s
return p, dp
def _scalar_func_3(self, s):
self.fcount += 1
p = -np.sin(10*s)
dp = -10*np.cos(10*s)
return p, dp
# -- n-d functions
def _line_func_1(self, x):
self.fcount += 1
f = np.dot(x, x)
df = 2*x
return f, df
def _line_func_2(self, x):
self.fcount += 1
f = np.dot(x, np.dot(self.A, x)) + 1
df = np.dot(self.A + self.A.T, x)
return f, df
# --
def setup_method(self):
self.scalar_funcs = []
self.line_funcs = []
self.N = 20
self.fcount = 0
def bind_index(func, idx):
# Remember Python's closure semantics!
return lambda *a, **kw: func(*a, **kw)[idx]
for name in sorted(dir(self)):
if name.startswith('_scalar_func_'):
value = getattr(self, name)
self.scalar_funcs.append(
(name, bind_index(value, 0), bind_index(value, 1)))
elif name.startswith('_line_func_'):
value = getattr(self, name)
self.line_funcs.append(
(name, bind_index(value, 0), bind_index(value, 1)))
np.random.seed(1234)
self.A = np.random.randn(self.N, self.N)
def scalar_iter(self):
for name, phi, derphi in self.scalar_funcs:
for old_phi0 in np.random.randn(3):
yield name, phi, derphi, old_phi0
def line_iter(self):
for name, f, fprime in self.line_funcs:
k = 0
while k < 9:
x = np.random.randn(self.N)
p = np.random.randn(self.N)
if np.dot(p, fprime(x)) >= 0:
# always pick a descent direction
continue
k += 1
old_fv = float(np.random.randn())
yield name, f, fprime, x, p, old_fv
# -- Generic scalar searches
def test_scalar_search_wolfe1(self):
c = 0
for name, phi, derphi, old_phi0 in self.scalar_iter():
c += 1
s, phi1, phi0 = ls.scalar_search_wolfe1(phi, derphi, phi(0),
old_phi0, derphi(0))
assert_fp_equal(phi0, phi(0), name)
assert_fp_equal(phi1, phi(s), name)
assert_wolfe(s, phi, derphi, err_msg=name)
assert_(c > 3) # check that the iterator really works...
def test_scalar_search_wolfe2(self):
for name, phi, derphi, old_phi0 in self.scalar_iter():
s, phi1, phi0, derphi1 = ls.scalar_search_wolfe2(
phi, derphi, phi(0), old_phi0, derphi(0))
assert_fp_equal(phi0, phi(0), name)
assert_fp_equal(phi1, phi(s), name)
if derphi1 is not None:
assert_fp_equal(derphi1, derphi(s), name)
assert_wolfe(s, phi, derphi, err_msg="%s %g" % (name, old_phi0))
def test_scalar_search_armijo(self):
for name, phi, derphi, old_phi0 in self.scalar_iter():
s, phi1 = ls.scalar_search_armijo(phi, phi(0), derphi(0))
assert_fp_equal(phi1, phi(s), name)
assert_armijo(s, phi, err_msg="%s %g" % (name, old_phi0))
# -- Generic line searches
def test_line_search_wolfe1(self):
c = 0
smax = 100
for name, f, fprime, x, p, old_f in self.line_iter():
f0 = f(x)
g0 = fprime(x)
self.fcount = 0
s, fc, gc, fv, ofv, gv = ls.line_search_wolfe1(f, fprime, x, p,
g0, f0, old_f,
amax=smax)
assert_equal(self.fcount, fc+gc)
assert_fp_equal(ofv, f(x))
if s is None:
continue
assert_fp_equal(fv, f(x + s*p))
assert_array_almost_equal(gv, fprime(x + s*p), decimal=14)
if s < smax:
c += 1
assert_line_wolfe(x, p, s, f, fprime, err_msg=name)
assert_(c > 3) # check that the iterator really works...
def test_line_search_wolfe2(self):
c = 0
smax = 512
for name, f, fprime, x, p, old_f in self.line_iter():
f0 = f(x)
g0 = fprime(x)
self.fcount = 0
with suppress_warnings() as sup:
sup.filter(LineSearchWarning,
"The line search algorithm could not find a solution")
sup.filter(LineSearchWarning,
"The line search algorithm did not converge")
s, fc, gc, fv, ofv, gv = ls.line_search_wolfe2(f, fprime, x, p,
g0, f0, old_f,
amax=smax)
assert_equal(self.fcount, fc+gc)
assert_fp_equal(ofv, f(x))
assert_fp_equal(fv, f(x + s*p))
if gv is not None:
assert_array_almost_equal(gv, fprime(x + s*p), decimal=14)
if s < smax:
c += 1
assert_line_wolfe(x, p, s, f, fprime, err_msg=name)
assert_(c > 3) # check that the iterator really works...
def test_line_search_wolfe2_bounds(self):
# See gh-7475
# For this f and p, starting at a point on axis 0, the strong Wolfe
# condition 2 is met if and only if the step length s satisfies
# |x + s| <= c2 * |x|
f = lambda x: np.dot(x, x)
fp = lambda x: 2 * x
p = np.array([1, 0])
# Smallest s satisfying strong Wolfe conditions for these arguments is 30
x = -60 * p
c2 = 0.5
s, _, _, _, _, _ = ls.line_search_wolfe2(f, fp, x, p, amax=30, c2=c2)
assert_line_wolfe(x, p, s, f, fp)
s, _, _, _, _, _ = assert_warns(LineSearchWarning,
ls.line_search_wolfe2, f, fp, x, p,
amax=29, c2=c2)
assert_(s is None)
# s=30 will only be tried on the 6th iteration, so this won't converge
assert_warns(LineSearchWarning, ls.line_search_wolfe2, f, fp, x, p,
c2=c2, maxiter=5)
def test_line_search_armijo(self):
c = 0
for name, f, fprime, x, p, old_f in self.line_iter():
f0 = f(x)
g0 = fprime(x)
self.fcount = 0
s, fc, fv = ls.line_search_armijo(f, x, p, g0, f0)
c += 1
assert_equal(self.fcount, fc)
assert_fp_equal(fv, f(x + s*p))
assert_line_armijo(x, p, s, f, err_msg=name)
assert_(c >= 9)
# -- More specific tests
def test_armijo_terminate_1(self):
# Armijo should evaluate the function only once if the trial step
# is already suitable
count = [0]
def phi(s):
count[0] += 1
return -s + 0.01*s**2
s, phi1 = ls.scalar_search_armijo(phi, phi(0), -1, alpha0=1)
assert_equal(s, 1)
assert_equal(count[0], 2)
assert_armijo(s, phi)
def test_wolfe_terminate(self):
# wolfe1 and wolfe2 should also evaluate the function only a few
# times if the trial step is already suitable
def phi(s):
count[0] += 1
return -s + 0.05*s**2
def derphi(s):
count[0] += 1
return -1 + 0.05*2*s
for func in [ls.scalar_search_wolfe1, ls.scalar_search_wolfe2]:
count = [0]
r = func(phi, derphi, phi(0), None, derphi(0))
assert_(r[0] is not None, (r, func))
assert_(count[0] <= 2 + 2, (count, func))
assert_wolfe(r[0], phi, derphi, err_msg=str(func))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,275 @@
from __future__ import division, absolute_import, print_function
from numpy.testing import assert_, assert_allclose, assert_equal
from pytest import raises as assert_raises
import numpy as np
from scipy.sparse.linalg import LinearOperator
from scipy.optimize._lsq.common import (
step_size_to_bound, find_active_constraints, make_strictly_feasible,
CL_scaling_vector, intersect_trust_region, build_quadratic_1d,
minimize_quadratic_1d, evaluate_quadratic, reflective_transformation,
left_multiplied_operator, right_multiplied_operator)
class TestBounds(object):
def test_step_size_to_bounds(self):
lb = np.array([-1.0, 2.5, 10.0])
ub = np.array([1.0, 5.0, 100.0])
x = np.array([0.0, 2.5, 12.0])
s = np.array([0.1, 0.0, 0.0])
step, hits = step_size_to_bound(x, s, lb, ub)
assert_equal(step, 10)
assert_equal(hits, [1, 0, 0])
s = np.array([0.01, 0.05, -1.0])
step, hits = step_size_to_bound(x, s, lb, ub)
assert_equal(step, 2)
assert_equal(hits, [0, 0, -1])
s = np.array([10.0, -0.0001, 100.0])
step, hits = step_size_to_bound(x, s, lb, ub)
assert_equal(step, np.array(-0))
assert_equal(hits, [0, -1, 0])
s = np.array([1.0, 0.5, -2.0])
step, hits = step_size_to_bound(x, s, lb, ub)
assert_equal(step, 1.0)
assert_equal(hits, [1, 0, -1])
s = np.zeros(3)
step, hits = step_size_to_bound(x, s, lb, ub)
assert_equal(step, np.inf)
assert_equal(hits, [0, 0, 0])
def test_find_active_constraints(self):
lb = np.array([0.0, -10.0, 1.0])
ub = np.array([1.0, 0.0, 100.0])
x = np.array([0.5, -5.0, 2.0])
active = find_active_constraints(x, lb, ub)
assert_equal(active, [0, 0, 0])
x = np.array([0.0, 0.0, 10.0])
active = find_active_constraints(x, lb, ub)
assert_equal(active, [-1, 1, 0])
active = find_active_constraints(x, lb, ub, rtol=0)
assert_equal(active, [-1, 1, 0])
x = np.array([1e-9, -1e-8, 100 - 1e-9])
active = find_active_constraints(x, lb, ub)
assert_equal(active, [0, 0, 1])
active = find_active_constraints(x, lb, ub, rtol=1.5e-9)
assert_equal(active, [-1, 0, 1])
lb = np.array([1.0, -np.inf, -np.inf])
ub = np.array([np.inf, 10.0, np.inf])
x = np.ones(3)
active = find_active_constraints(x, lb, ub)
assert_equal(active, [-1, 0, 0])
# Handles out-of-bound cases.
x = np.array([0.0, 11.0, 0.0])
active = find_active_constraints(x, lb, ub)
assert_equal(active, [-1, 1, 0])
active = find_active_constraints(x, lb, ub, rtol=0)
assert_equal(active, [-1, 1, 0])
def test_make_strictly_feasible(self):
lb = np.array([-0.5, -0.8, 2.0])
ub = np.array([0.8, 1.0, 3.0])
x = np.array([-0.5, 0.0, 2 + 1e-10])
x_new = make_strictly_feasible(x, lb, ub, rstep=0)
assert_(x_new[0] > -0.5)
assert_equal(x_new[1:], x[1:])
x_new = make_strictly_feasible(x, lb, ub, rstep=1e-4)
assert_equal(x_new, [-0.5 + 1e-4, 0.0, 2 * (1 + 1e-4)])
x = np.array([-0.5, -1, 3.1])
x_new = make_strictly_feasible(x, lb, ub)
assert_(np.all((x_new >= lb) & (x_new <= ub)))
x_new = make_strictly_feasible(x, lb, ub, rstep=0)
assert_(np.all((x_new >= lb) & (x_new <= ub)))
lb = np.array([-1, 100.0])
ub = np.array([1, 100.0 + 1e-10])
x = np.array([0, 100.0])
x_new = make_strictly_feasible(x, lb, ub, rstep=1e-8)
assert_equal(x_new, [0, 100.0 + 0.5e-10])
def test_scaling_vector(self):
lb = np.array([-np.inf, -5.0, 1.0, -np.inf])
ub = np.array([1.0, np.inf, 10.0, np.inf])
x = np.array([0.5, 2.0, 5.0, 0.0])
g = np.array([1.0, 0.1, -10.0, 0.0])
v, dv = CL_scaling_vector(x, g, lb, ub)
assert_equal(v, [1.0, 7.0, 5.0, 1.0])
assert_equal(dv, [0.0, 1.0, -1.0, 0.0])
class TestQuadraticFunction(object):
def setup_method(self):
self.J = np.array([
[0.1, 0.2],
[-1.0, 1.0],
[0.5, 0.2]])
self.g = np.array([0.8, -2.0])
self.diag = np.array([1.0, 2.0])
def test_build_quadratic_1d(self):
s = np.zeros(2)
a, b = build_quadratic_1d(self.J, self.g, s)
assert_equal(a, 0)
assert_equal(b, 0)
a, b = build_quadratic_1d(self.J, self.g, s, diag=self.diag)
assert_equal(a, 0)
assert_equal(b, 0)
s = np.array([1.0, -1.0])
a, b = build_quadratic_1d(self.J, self.g, s)
assert_equal(a, 2.05)
assert_equal(b, 2.8)
a, b = build_quadratic_1d(self.J, self.g, s, diag=self.diag)
assert_equal(a, 3.55)
assert_equal(b, 2.8)
s0 = np.array([0.5, 0.5])
a, b, c = build_quadratic_1d(self.J, self.g, s, diag=self.diag, s0=s0)
assert_equal(a, 3.55)
assert_allclose(b, 2.39)
assert_allclose(c, -0.1525)
def test_minimize_quadratic_1d(self):
a = 5
b = -1
t, y = minimize_quadratic_1d(a, b, 1, 2)
assert_equal(t, 1)
assert_equal(y, a * t**2 + b * t)
t, y = minimize_quadratic_1d(a, b, -2, -1)
assert_equal(t, -1)
assert_equal(y, a * t**2 + b * t)
t, y = minimize_quadratic_1d(a, b, -1, 1)
assert_equal(t, 0.1)
assert_equal(y, a * t**2 + b * t)
c = 10
t, y = minimize_quadratic_1d(a, b, -1, 1, c=c)
assert_equal(t, 0.1)
assert_equal(y, a * t**2 + b * t + c)
def test_evaluate_quadratic(self):
s = np.array([1.0, -1.0])
value = evaluate_quadratic(self.J, self.g, s)
assert_equal(value, 4.85)
value = evaluate_quadratic(self.J, self.g, s, diag=self.diag)
assert_equal(value, 6.35)
s = np.array([[1.0, -1.0],
[1.0, 1.0],
[0.0, 0.0]])
values = evaluate_quadratic(self.J, self.g, s)
assert_allclose(values, [4.85, -0.91, 0.0])
values = evaluate_quadratic(self.J, self.g, s, diag=self.diag)
assert_allclose(values, [6.35, 0.59, 0.0])
class TestTrustRegion(object):
def test_intersect(self):
Delta = 1.0
x = np.zeros(3)
s = np.array([1.0, 0.0, 0.0])
t_neg, t_pos = intersect_trust_region(x, s, Delta)
assert_equal(t_neg, -1)
assert_equal(t_pos, 1)
s = np.array([-1.0, 1.0, -1.0])
t_neg, t_pos = intersect_trust_region(x, s, Delta)
assert_allclose(t_neg, -3**-0.5)
assert_allclose(t_pos, 3**-0.5)
x = np.array([0.5, -0.5, 0])
s = np.array([0, 0, 1.0])
t_neg, t_pos = intersect_trust_region(x, s, Delta)
assert_allclose(t_neg, -2**-0.5)
assert_allclose(t_pos, 2**-0.5)
x = np.ones(3)
assert_raises(ValueError, intersect_trust_region, x, s, Delta)
x = np.zeros(3)
s = np.zeros(3)
assert_raises(ValueError, intersect_trust_region, x, s, Delta)
def test_reflective_transformation():
lb = np.array([-1, -2], dtype=float)
ub = np.array([5, 3], dtype=float)
y = np.array([0, 0])
x, g = reflective_transformation(y, lb, ub)
assert_equal(x, y)
assert_equal(g, np.ones(2))
y = np.array([-4, 4], dtype=float)
x, g = reflective_transformation(y, lb, np.array([np.inf, np.inf]))
assert_equal(x, [2, 4])
assert_equal(g, [-1, 1])
x, g = reflective_transformation(y, np.array([-np.inf, -np.inf]), ub)
assert_equal(x, [-4, 2])
assert_equal(g, [1, -1])
x, g = reflective_transformation(y, lb, ub)
assert_equal(x, [2, 2])
assert_equal(g, [-1, -1])
lb = np.array([-np.inf, -2])
ub = np.array([5, np.inf])
y = np.array([10, 10], dtype=float)
x, g = reflective_transformation(y, lb, ub)
assert_equal(x, [0, 10])
assert_equal(g, [-1, 1])
def test_linear_operators():
A = np.arange(6).reshape((3, 2))
d_left = np.array([-1, 2, 5])
DA = np.diag(d_left).dot(A)
J_left = left_multiplied_operator(A, d_left)
d_right = np.array([5, 10])
AD = A.dot(np.diag(d_right))
J_right = right_multiplied_operator(A, d_right)
x = np.array([-2, 3])
X = -2 * np.arange(2, 8).reshape((2, 3))
xt = np.array([0, -2, 15])
assert_allclose(DA.dot(x), J_left.matvec(x))
assert_allclose(DA.dot(X), J_left.matmat(X))
assert_allclose(DA.T.dot(xt), J_left.rmatvec(xt))
assert_allclose(AD.dot(x), J_right.matvec(x))
assert_allclose(AD.dot(X), J_right.matmat(X))
assert_allclose(AD.T.dot(xt), J_right.rmatvec(xt))
@@ -0,0 +1,150 @@
import numpy as np
from numpy.linalg import lstsq
from numpy.testing import assert_allclose, assert_equal, assert_
from pytest import raises as assert_raises
from scipy.sparse import rand
from scipy.sparse.linalg import aslinearoperator
from scipy.optimize import lsq_linear
A = np.array([
[0.171, -0.057],
[-0.049, -0.248],
[-0.166, 0.054],
])
b = np.array([0.074, 1.014, -0.383])
class BaseMixin(object):
def setup_method(self):
self.rnd = np.random.RandomState(0)
def test_dense_no_bounds(self):
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, method=self.method, lsq_solver=lsq_solver)
assert_allclose(res.x, lstsq(A, b, rcond=-1)[0])
def test_dense_bounds(self):
# Solutions for comparison are taken from MATLAB.
lb = np.array([-1, -10])
ub = np.array([1, 0])
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (lb, ub), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, lstsq(A, b, rcond=-1)[0])
lb = np.array([0.0, -np.inf])
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (lb, np.inf), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, np.array([0.0, -4.084174437334673]),
atol=1e-6)
lb = np.array([-1, 0])
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (lb, np.inf), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, np.array([0.448427311733504, 0]),
atol=1e-15)
ub = np.array([np.inf, -5])
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (-np.inf, ub), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, np.array([-0.105560998682388, -5]))
ub = np.array([-1, np.inf])
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (-np.inf, ub), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, np.array([-1, -4.181102129483254]))
lb = np.array([0, -4])
ub = np.array([1, 0])
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (lb, ub), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, np.array([0.005236663400791, -4]))
def test_dense_rank_deficient(self):
A = np.array([[-0.307, -0.184]])
b = np.array([0.773])
lb = [-0.1, -0.1]
ub = [0.1, 0.1]
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (lb, ub), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.x, [-0.1, -0.1])
A = np.array([
[0.334, 0.668],
[-0.516, -1.032],
[0.192, 0.384],
])
b = np.array([-1.436, 0.135, 0.909])
lb = [0, -1]
ub = [1, -0.5]
for lsq_solver in self.lsq_solvers:
res = lsq_linear(A, b, (lb, ub), method=self.method,
lsq_solver=lsq_solver)
assert_allclose(res.optimality, 0, atol=1e-11)
def test_full_result(self):
lb = np.array([0, -4])
ub = np.array([1, 0])
res = lsq_linear(A, b, (lb, ub), method=self.method)
assert_allclose(res.x, [0.005236663400791, -4])
r = A.dot(res.x) - b
assert_allclose(res.cost, 0.5 * np.dot(r, r))
assert_allclose(res.fun, r)
assert_allclose(res.optimality, 0.0, atol=1e-12)
assert_equal(res.active_mask, [0, -1])
assert_(res.nit < 15)
assert_(res.status == 1 or res.status == 3)
assert_(isinstance(res.message, str))
assert_(res.success)
class SparseMixin(object):
def test_sparse_and_LinearOperator(self):
m = 5000
n = 1000
A = rand(m, n, random_state=0)
b = self.rnd.randn(m)
res = lsq_linear(A, b)
assert_allclose(res.optimality, 0, atol=1e-6)
A = aslinearoperator(A)
res = lsq_linear(A, b)
assert_allclose(res.optimality, 0, atol=1e-6)
def test_sparse_bounds(self):
m = 5000
n = 1000
A = rand(m, n, random_state=0)
b = self.rnd.randn(m)
lb = self.rnd.randn(n)
ub = lb + 1
res = lsq_linear(A, b, (lb, ub))
assert_allclose(res.optimality, 0.0, atol=1e-6)
res = lsq_linear(A, b, (lb, ub), lsmr_tol=1e-13)
assert_allclose(res.optimality, 0.0, atol=1e-6)
res = lsq_linear(A, b, (lb, ub), lsmr_tol='auto')
assert_allclose(res.optimality, 0.0, atol=1e-6)
class TestTRF(BaseMixin, SparseMixin):
method = 'trf'
lsq_solvers = ['exact', 'lsmr']
class TestBVLS(BaseMixin):
method = 'bvls'
lsq_solvers = ['exact']
@@ -0,0 +1,617 @@
from __future__ import division, print_function, absolute_import
import numpy as np
import pytest
from scipy.linalg import block_diag
from scipy.sparse import csc_matrix
from numpy.testing import (TestCase, assert_array_almost_equal,
assert_array_less, assert_allclose, assert_)
from pytest import raises
from scipy.optimize import (NonlinearConstraint,
LinearConstraint,
Bounds,
minimize,
BFGS,
SR1)
from scipy._lib._numpy_compat import suppress_warnings
class Maratos:
"""Problem 15.4 from Nocedal and Wright
The following optimization problem:
minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
Subject to: x[0]**2 + x[1]**2 - 1 = 0
"""
def __init__(self, degrees=60, constr_jac=None, constr_hess=None):
rads = degrees/180*np.pi
self.x0 = [np.cos(rads), np.sin(rads)]
self.x_opt = np.array([1.0, 0.0])
self.constr_jac = constr_jac
self.constr_hess = constr_hess
self.bounds = None
def fun(self, x):
return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
def grad(self, x):
return np.array([4*x[0]-1, 4*x[1]])
def hess(self, x):
return 4*np.eye(2)
@property
def constr(self):
def fun(x):
return x[0]**2 + x[1]**2
if self.constr_jac is None:
def jac(x):
return [[2*x[0], 2*x[1]]]
else:
jac = self.constr_jac
if self.constr_hess is None:
def hess(x, v):
return 2*v[0]*np.eye(2)
else:
hess = self.constr_hess
return NonlinearConstraint(fun, 1, 1, jac, hess)
class MaratosTestArgs:
"""Problem 15.4 from Nocedal and Wright
The following optimization problem:
minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
Subject to: x[0]**2 + x[1]**2 - 1 = 0
"""
def __init__(self, a, b, degrees=60, constr_jac=None, constr_hess=None):
rads = degrees/180*np.pi
self.x0 = [np.cos(rads), np.sin(rads)]
self.x_opt = np.array([1.0, 0.0])
self.constr_jac = constr_jac
self.constr_hess = constr_hess
self.a = a
self.b = b
self.bounds = None
def _test_args(self, a, b):
if self.a != a or self.b != b:
raise ValueError()
def fun(self, x, a, b):
self._test_args(a, b)
return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
def grad(self, x, a, b):
self._test_args(a, b)
return np.array([4*x[0]-1, 4*x[1]])
def hess(self, x, a, b):
self._test_args(a, b)
return 4*np.eye(2)
@property
def constr(self):
def fun(x):
return x[0]**2 + x[1]**2
if self.constr_jac is None:
def jac(x):
return [[4*x[0], 4*x[1]]]
else:
jac = self.constr_jac
if self.constr_hess is None:
def hess(x, v):
return 2*v[0]*np.eye(2)
else:
hess = self.constr_hess
return NonlinearConstraint(fun, 1, 1, jac, hess)
class MaratosGradInFunc:
"""Problem 15.4 from Nocedal and Wright
The following optimization problem:
minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
Subject to: x[0]**2 + x[1]**2 - 1 = 0
"""
def __init__(self, degrees=60, constr_jac=None, constr_hess=None):
rads = degrees/180*np.pi
self.x0 = [np.cos(rads), np.sin(rads)]
self.x_opt = np.array([1.0, 0.0])
self.constr_jac = constr_jac
self.constr_hess = constr_hess
self.bounds = None
def fun(self, x):
return (2*(x[0]**2 + x[1]**2 - 1) - x[0],
np.array([4*x[0]-1, 4*x[1]]))
@property
def grad(self):
return True
def hess(self, x):
return 4*np.eye(2)
@property
def constr(self):
def fun(x):
return x[0]**2 + x[1]**2
if self.constr_jac is None:
def jac(x):
return [[4*x[0], 4*x[1]]]
else:
jac = self.constr_jac
if self.constr_hess is None:
def hess(x, v):
return 2*v[0]*np.eye(2)
else:
hess = self.constr_hess
return NonlinearConstraint(fun, 1, 1, jac, hess)
class HyperbolicIneq:
"""Problem 15.1 from Nocedal and Wright
The following optimization problem:
minimize 1/2*(x[0] - 2)**2 + 1/2*(x[1] - 1/2)**2
Subject to: 1/(x[0] + 1) - x[1] >= 1/4
x[0] >= 0
x[1] >= 0
"""
def __init__(self, constr_jac=None, constr_hess=None):
self.x0 = [0, 0]
self.x_opt = [1.952823, 0.088659]
self.constr_jac = constr_jac
self.constr_hess = constr_hess
self.bounds = Bounds(0, np.inf)
def fun(self, x):
return 1/2*(x[0] - 2)**2 + 1/2*(x[1] - 1/2)**2
def grad(self, x):
return [x[0] - 2, x[1] - 1/2]
def hess(self, x):
return np.eye(2)
@property
def constr(self):
def fun(x):
return 1/(x[0] + 1) - x[1]
if self.constr_jac is None:
def jac(x):
return [[-1/(x[0] + 1)**2, -1]]
else:
jac = self.constr_jac
if self.constr_hess is None:
def hess(x, v):
return 2*v[0]*np.array([[1/(x[0] + 1)**3, 0],
[0, 0]])
else:
hess = self.constr_hess
return NonlinearConstraint(fun, 0.25, np.inf, jac, hess)
class Rosenbrock:
"""Rosenbrock function.
The following optimization problem:
minimize sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
"""
def __init__(self, n=2, random_state=0):
rng = np.random.RandomState(random_state)
self.x0 = rng.uniform(-1, 1, n)
self.x_opt = np.ones(n)
self.bounds = None
def fun(self, x):
x = np.asarray(x)
r = np.sum(100.0 * (x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0,
axis=0)
return r
def grad(self, x):
x = np.asarray(x)
xm = x[1:-1]
xm_m1 = x[:-2]
xm_p1 = x[2:]
der = np.zeros_like(x)
der[1:-1] = (200 * (xm - xm_m1**2) -
400 * (xm_p1 - xm**2) * xm - 2 * (1 - xm))
der[0] = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
der[-1] = 200 * (x[-1] - x[-2]**2)
return der
def hess(self, x):
x = np.atleast_1d(x)
H = np.diag(-400 * x[:-1], 1) - np.diag(400 * x[:-1], -1)
diagonal = np.zeros(len(x), dtype=x.dtype)
diagonal[0] = 1200 * x[0]**2 - 400 * x[1] + 2
diagonal[-1] = 200
diagonal[1:-1] = 202 + 1200 * x[1:-1]**2 - 400 * x[2:]
H = H + np.diag(diagonal)
return H
@property
def constr(self):
return ()
class IneqRosenbrock(Rosenbrock):
"""Rosenbrock subject to inequality constraints.
The following optimization problem:
minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
subject to: x[0] + 2 x[1] <= 1
Taken from matlab ``fmincon`` documentation.
"""
def __init__(self, random_state=0):
Rosenbrock.__init__(self, 2, random_state)
self.x0 = [-1, -0.5]
self.x_opt = [0.5022, 0.2489]
self.bounds = None
@property
def constr(self):
A = [[1, 2]]
b = 1
return LinearConstraint(A, -np.inf, b)
class BoundedRosenbrock(Rosenbrock):
"""Rosenbrock subject to inequality constraints.
The following optimization problem:
minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
subject to: -2 <= x[0] <= 0
0 <= x[1] <= 2
Taken from matlab ``fmincon`` documentation.
"""
def __init__(self, random_state=0):
Rosenbrock.__init__(self, 2, random_state)
self.x0 = [-0.2, 0.2]
self.x_opt = None
self.bounds = Bounds([-2, 0], [0, 2])
class EqIneqRosenbrock(Rosenbrock):
"""Rosenbrock subject to equality and inequality constraints.
The following optimization problem:
minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
subject to: x[0] + 2 x[1] <= 1
2 x[0] + x[1] = 1
Taken from matlab ``fimincon`` documentation.
"""
def __init__(self, random_state=0):
Rosenbrock.__init__(self, 2, random_state)
self.x0 = [-1, -0.5]
self.x_opt = [0.41494, 0.17011]
self.bounds = None
@property
def constr(self):
A_ineq = [[1, 2]]
b_ineq = 1
A_eq = [[2, 1]]
b_eq = 1
return (LinearConstraint(A_ineq, -np.inf, b_ineq),
LinearConstraint(A_eq, b_eq, b_eq))
class Elec:
"""Distribution of electrons on a sphere.
Problem no 2 from COPS collection [2]_. Find
the equilibrium state distribution (of minimal
potential) of the electrons positioned on a
conducting sphere.
References
----------
.. [1] E. D. Dolan, J. J. Mor\'{e}, and T. S. Munson,
"Benchmarking optimization software with COPS 3.0.",
Argonne National Lab., Argonne, IL (US), 2004.
"""
def __init__(self, n_electrons=200, random_state=0,
constr_jac=None, constr_hess=None):
self.n_electrons = n_electrons
self.rng = np.random.RandomState(random_state)
# Initial Guess
phi = self.rng.uniform(0, 2 * np.pi, self.n_electrons)
theta = self.rng.uniform(-np.pi, np.pi, self.n_electrons)
x = np.cos(theta) * np.cos(phi)
y = np.cos(theta) * np.sin(phi)
z = np.sin(theta)
self.x0 = np.hstack((x, y, z))
self.x_opt = None
self.constr_jac = constr_jac
self.constr_hess = constr_hess
self.bounds = None
def _get_cordinates(self, x):
x_coord = x[:self.n_electrons]
y_coord = x[self.n_electrons:2 * self.n_electrons]
z_coord = x[2 * self.n_electrons:]
return x_coord, y_coord, z_coord
def _compute_coordinate_deltas(self, x):
x_coord, y_coord, z_coord = self._get_cordinates(x)
dx = x_coord[:, None] - x_coord
dy = y_coord[:, None] - y_coord
dz = z_coord[:, None] - z_coord
return dx, dy, dz
def fun(self, x):
dx, dy, dz = self._compute_coordinate_deltas(x)
with np.errstate(divide='ignore'):
dm1 = (dx**2 + dy**2 + dz**2) ** -0.5
dm1[np.diag_indices_from(dm1)] = 0
return 0.5 * np.sum(dm1)
def grad(self, x):
dx, dy, dz = self._compute_coordinate_deltas(x)
with np.errstate(divide='ignore'):
dm3 = (dx**2 + dy**2 + dz**2) ** -1.5
dm3[np.diag_indices_from(dm3)] = 0
grad_x = -np.sum(dx * dm3, axis=1)
grad_y = -np.sum(dy * dm3, axis=1)
grad_z = -np.sum(dz * dm3, axis=1)
return np.hstack((grad_x, grad_y, grad_z))
def hess(self, x):
dx, dy, dz = self._compute_coordinate_deltas(x)
d = (dx**2 + dy**2 + dz**2) ** 0.5
with np.errstate(divide='ignore'):
dm3 = d ** -3
dm5 = d ** -5
i = np.arange(self.n_electrons)
dm3[i, i] = 0
dm5[i, i] = 0
Hxx = dm3 - 3 * dx**2 * dm5
Hxx[i, i] = -np.sum(Hxx, axis=1)
Hxy = -3 * dx * dy * dm5
Hxy[i, i] = -np.sum(Hxy, axis=1)
Hxz = -3 * dx * dz * dm5
Hxz[i, i] = -np.sum(Hxz, axis=1)
Hyy = dm3 - 3 * dy**2 * dm5
Hyy[i, i] = -np.sum(Hyy, axis=1)
Hyz = -3 * dy * dz * dm5
Hyz[i, i] = -np.sum(Hyz, axis=1)
Hzz = dm3 - 3 * dz**2 * dm5
Hzz[i, i] = -np.sum(Hzz, axis=1)
H = np.vstack((
np.hstack((Hxx, Hxy, Hxz)),
np.hstack((Hxy, Hyy, Hyz)),
np.hstack((Hxz, Hyz, Hzz))
))
return H
@property
def constr(self):
def fun(x):
x_coord, y_coord, z_coord = self._get_cordinates(x)
return x_coord**2 + y_coord**2 + z_coord**2 - 1
if self.constr_jac is None:
def jac(x):
x_coord, y_coord, z_coord = self._get_cordinates(x)
Jx = 2 * np.diag(x_coord)
Jy = 2 * np.diag(y_coord)
Jz = 2 * np.diag(z_coord)
return csc_matrix(np.hstack((Jx, Jy, Jz)))
else:
jac = self.constr_jac
if self.constr_hess is None:
def hess(x, v):
D = 2 * np.diag(v)
return block_diag(D, D, D)
else:
hess = self.constr_hess
return NonlinearConstraint(fun, -np.inf, 0, jac, hess)
class TestTrustRegionConstr(TestCase):
@pytest.mark.slow
def test_list_of_problems(self):
list_of_problems = [Maratos(),
Maratos(constr_hess='2-point'),
Maratos(constr_hess=SR1()),
Maratos(constr_jac='2-point', constr_hess=SR1()),
MaratosGradInFunc(),
HyperbolicIneq(),
HyperbolicIneq(constr_hess='3-point'),
HyperbolicIneq(constr_hess=BFGS()),
HyperbolicIneq(constr_jac='3-point',
constr_hess=BFGS()),
Rosenbrock(),
IneqRosenbrock(),
EqIneqRosenbrock(),
BoundedRosenbrock(),
Elec(n_electrons=2),
Elec(n_electrons=2, constr_hess='2-point'),
Elec(n_electrons=2, constr_hess=SR1()),
Elec(n_electrons=2, constr_jac='3-point',
constr_hess=SR1())]
for prob in list_of_problems:
for grad in (prob.grad, '3-point', False):
for hess in (prob.hess,
'3-point',
SR1(),
BFGS(exception_strategy='damp_update'),
BFGS(exception_strategy='skip_update')):
# Remove exceptions
if grad in ('2-point', '3-point', 'cs', False) and \
hess in ('2-point', '3-point', 'cs'):
continue
if prob.grad is True and grad in ('3-point', False):
continue
with suppress_warnings() as sup:
sup.filter(UserWarning, "delta_grad == 0.0")
result = minimize(prob.fun, prob.x0,
method='trust-constr',
jac=grad, hess=hess,
bounds=prob.bounds,
constraints=prob.constr)
if prob.x_opt is not None:
assert_array_almost_equal(result.x, prob.x_opt,
decimal=5)
# gtol
if result.status == 1:
assert_array_less(result.optimality, 1e-8)
# xtol
if result.status == 2:
assert_array_less(result.tr_radius, 1e-8)
if result.method == "tr_interior_point":
assert_array_less(result.barrier_parameter, 1e-8)
# max iter
if result.status in (0, 3):
raise RuntimeError("Invalid termination condition.")
def test_default_jac_and_hess(self):
def fun(x):
return (x - 1) ** 2
bounds = [(-2, 2)]
res = minimize(fun, x0=[-1.5], bounds=bounds, method='trust-constr')
assert_array_almost_equal(res.x, 1, decimal=5)
def test_default_hess(self):
def fun(x):
return (x - 1) ** 2
bounds = [(-2, 2)]
res = minimize(fun, x0=[-1.5], bounds=bounds, method='trust-constr',
jac='2-point')
assert_array_almost_equal(res.x, 1, decimal=5)
def test_no_constraints(self):
prob = Rosenbrock()
result = minimize(prob.fun, prob.x0,
method='trust-constr',
jac=prob.grad, hess=prob.hess)
result1 = minimize(prob.fun, prob.x0,
method='L-BFGS-B',
jac='2-point')
with pytest.warns(UserWarning):
result2 = minimize(prob.fun, prob.x0,
method='L-BFGS-B',
jac='3-point')
assert_array_almost_equal(result.x, prob.x_opt, decimal=5)
assert_array_almost_equal(result1.x, prob.x_opt, decimal=5)
assert_array_almost_equal(result2.x, prob.x_opt, decimal=5)
def test_hessp(self):
prob = Maratos()
def hessp(x, p):
H = prob.hess(x)
return H.dot(p)
result = minimize(prob.fun, prob.x0,
method='trust-constr',
jac=prob.grad, hessp=hessp,
bounds=prob.bounds,
constraints=prob.constr)
if prob.x_opt is not None:
assert_array_almost_equal(result.x, prob.x_opt, decimal=2)
# gtol
if result.status == 1:
assert_array_less(result.optimality, 1e-8)
# xtol
if result.status == 2:
assert_array_less(result.tr_radius, 1e-8)
if result.method == "tr_interior_point":
assert_array_less(result.barrier_parameter, 1e-8)
# max iter
if result.status in (0, 3):
raise RuntimeError("Invalid termination condition.")
def test_args(self):
prob = MaratosTestArgs("a", 234)
result = minimize(prob.fun, prob.x0, ("a", 234),
method='trust-constr',
jac=prob.grad, hess=prob.hess,
bounds=prob.bounds,
constraints=prob.constr)
if prob.x_opt is not None:
assert_array_almost_equal(result.x, prob.x_opt, decimal=2)
# gtol
if result.status == 1:
assert_array_less(result.optimality, 1e-8)
# xtol
if result.status == 2:
assert_array_less(result.tr_radius, 1e-8)
if result.method == "tr_interior_point":
assert_array_less(result.barrier_parameter, 1e-8)
# max iter
if result.status in (0, 3):
raise RuntimeError("Invalid termination condition.")
def test_raise_exception(self):
prob = Maratos()
raises(ValueError, minimize, prob.fun, prob.x0, method='trust-constr',
jac='2-point', hess='2-point', constraints=prob.constr)
def test_issue_9044(self):
# https://github.com/scipy/scipy/issues/9044
# Test the returned `OptimizeResult` contains keys consistent with
# other solvers.
def callback(x, info):
assert_('nit' in info)
assert_('niter' in info)
result = minimize(lambda x: x**2, [0], jac=lambda x: 2*x,
hess=lambda x: 2, callback=callback,
method='trust-constr')
assert_(result.get('success'))
assert_(result.get('nit', -1) == 1)
# Also check existence of the 'niter' attribute, for backward
# compatibility
assert_(result.get('niter', -1) == 1)
@@ -0,0 +1,790 @@
"""
Unit tests for optimization routines from minpack.py.
"""
from __future__ import division, print_function, absolute_import
from numpy.testing import (assert_, assert_almost_equal, assert_array_equal,
assert_array_almost_equal, assert_allclose)
from pytest import raises as assert_raises
import numpy as np
from numpy import array, float64, matrix
from multiprocessing.pool import ThreadPool
from scipy import optimize
from scipy.special import lambertw
from scipy.optimize.minpack import leastsq, curve_fit, fixed_point
from scipy._lib._numpy_compat import _assert_warns, suppress_warnings
from scipy.optimize import OptimizeWarning
class ReturnShape(object):
"""This class exists to create a callable that does not have a '__name__' attribute.
__init__ takes the argument 'shape', which should be a tuple of ints. When an instance
it called with a single argument 'x', it returns numpy.ones(shape).
"""
def __init__(self, shape):
self.shape = shape
def __call__(self, x):
return np.ones(self.shape)
def dummy_func(x, shape):
"""A function that returns an array of ones of the given shape.
`x` is ignored.
"""
return np.ones(shape)
def sequence_parallel(fs):
pool = ThreadPool(len(fs))
try:
return pool.map(lambda f: f(), fs)
finally:
pool.terminate()
# Function and jacobian for tests of solvers for systems of nonlinear
# equations
def pressure_network(flow_rates, Qtot, k):
"""Evaluate non-linear equation system representing
the pressures and flows in a system of n parallel pipes::
f_i = P_i - P_0, for i = 1..n
f_0 = sum(Q_i) - Qtot
Where Q_i is the flow rate in pipe i and P_i the pressure in that pipe.
Pressure is modeled as a P=kQ**2 where k is a valve coefficient and
Q is the flow rate.
Parameters
----------
flow_rates : float
A 1D array of n flow rates [kg/s].
k : float
A 1D array of n valve coefficients [1/kg m].
Qtot : float
A scalar, the total input flow rate [kg/s].
Returns
-------
F : float
A 1D array, F[i] == f_i.
"""
P = k * flow_rates**2
F = np.hstack((P[1:] - P[0], flow_rates.sum() - Qtot))
return F
def pressure_network_jacobian(flow_rates, Qtot, k):
"""Return the jacobian of the equation system F(flow_rates)
computed by `pressure_network` with respect to
*flow_rates*. See `pressure_network` for the detailed
description of parrameters.
Returns
-------
jac : float
*n* by *n* matrix ``df_i/dQ_i`` where ``n = len(flow_rates)``
and *f_i* and *Q_i* are described in the doc for `pressure_network`
"""
n = len(flow_rates)
pdiff = np.diag(flow_rates[1:] * 2 * k[1:] - 2 * flow_rates[0] * k[0])
jac = np.empty((n, n))
jac[:n-1, :n-1] = pdiff * 0
jac[:n-1, n-1] = 0
jac[n-1, :] = np.ones(n)
return jac
def pressure_network_fun_and_grad(flow_rates, Qtot, k):
return (pressure_network(flow_rates, Qtot, k),
pressure_network_jacobian(flow_rates, Qtot, k))
class TestFSolve(object):
def test_pressure_network_no_gradient(self):
# fsolve without gradient, equal pipes -> equal flows.
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows, info, ier, mesg = optimize.fsolve(
pressure_network, initial_guess, args=(Qtot, k),
full_output=True)
assert_array_almost_equal(final_flows, np.ones(4))
assert_(ier == 1, mesg)
def test_pressure_network_with_gradient(self):
# fsolve with gradient, equal pipes -> equal flows
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows = optimize.fsolve(
pressure_network, initial_guess, args=(Qtot, k),
fprime=pressure_network_jacobian)
assert_array_almost_equal(final_flows, np.ones(4))
def test_wrong_shape_func_callable(self):
func = ReturnShape(1)
# x0 is a list of two elements, but func will return an array with
# length 1, so this should result in a TypeError.
x0 = [1.5, 2.0]
assert_raises(TypeError, optimize.fsolve, func, x0)
def test_wrong_shape_func_function(self):
# x0 is a list of two elements, but func will return an array with
# length 1, so this should result in a TypeError.
x0 = [1.5, 2.0]
assert_raises(TypeError, optimize.fsolve, dummy_func, x0, args=((1,),))
def test_wrong_shape_fprime_callable(self):
func = ReturnShape(1)
deriv_func = ReturnShape((2,2))
assert_raises(TypeError, optimize.fsolve, func, x0=[0,1], fprime=deriv_func)
def test_wrong_shape_fprime_function(self):
func = lambda x: dummy_func(x, (2,))
deriv_func = lambda x: dummy_func(x, (3,3))
assert_raises(TypeError, optimize.fsolve, func, x0=[0,1], fprime=deriv_func)
def test_func_can_raise(self):
def func(*args):
raise ValueError('I raised')
with assert_raises(ValueError, match='I raised'):
optimize.fsolve(func, x0=[0])
def test_Dfun_can_raise(self):
func = lambda x: x - np.array([10])
def deriv_func(*args):
raise ValueError('I raised')
with assert_raises(ValueError, match='I raised'):
optimize.fsolve(func, x0=[0], fprime=deriv_func)
def test_float32(self):
func = lambda x: np.array([x[0] - 100, x[1] - 1000], dtype=np.float32)**2
p = optimize.fsolve(func, np.array([1, 1], np.float32))
assert_allclose(func(p), [0, 0], atol=1e-3)
def test_reentrant_func(self):
def func(*args):
self.test_pressure_network_no_gradient()
return pressure_network(*args)
# fsolve without gradient, equal pipes -> equal flows.
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows, info, ier, mesg = optimize.fsolve(
func, initial_guess, args=(Qtot, k),
full_output=True)
assert_array_almost_equal(final_flows, np.ones(4))
assert_(ier == 1, mesg)
def test_reentrant_Dfunc(self):
def deriv_func(*args):
self.test_pressure_network_with_gradient()
return pressure_network_jacobian(*args)
# fsolve with gradient, equal pipes -> equal flows
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows = optimize.fsolve(
pressure_network, initial_guess, args=(Qtot, k),
fprime=deriv_func)
assert_array_almost_equal(final_flows, np.ones(4))
def test_concurrent_no_gradient(self):
return sequence_parallel([self.test_pressure_network_no_gradient] * 10)
def test_concurrent_with_gradient(self):
return sequence_parallel([self.test_pressure_network_with_gradient] * 10)
class TestRootHybr(object):
def test_pressure_network_no_gradient(self):
# root/hybr without gradient, equal pipes -> equal flows
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows = optimize.root(pressure_network, initial_guess,
method='hybr', args=(Qtot, k)).x
assert_array_almost_equal(final_flows, np.ones(4))
def test_pressure_network_with_gradient(self):
# root/hybr with gradient, equal pipes -> equal flows
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = matrix([2., 0., 2., 0.])
final_flows = optimize.root(pressure_network, initial_guess,
args=(Qtot, k), method='hybr',
jac=pressure_network_jacobian).x
assert_array_almost_equal(final_flows, np.ones(4))
def test_pressure_network_with_gradient_combined(self):
# root/hybr with gradient and function combined, equal pipes -> equal
# flows
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows = optimize.root(pressure_network_fun_and_grad,
initial_guess, args=(Qtot, k),
method='hybr', jac=True).x
assert_array_almost_equal(final_flows, np.ones(4))
class TestRootLM(object):
def test_pressure_network_no_gradient(self):
# root/lm without gradient, equal pipes -> equal flows
k = np.ones(4) * 0.5
Qtot = 4
initial_guess = array([2., 0., 2., 0.])
final_flows = optimize.root(pressure_network, initial_guess,
method='lm', args=(Qtot, k)).x
assert_array_almost_equal(final_flows, np.ones(4))
class TestLeastSq(object):
def setup_method(self):
x = np.linspace(0, 10, 40)
a,b,c = 3.1, 42, -304.2
self.x = x
self.abc = a,b,c
y_true = a*x**2 + b*x + c
np.random.seed(0)
self.y_meas = y_true + 0.01*np.random.standard_normal(y_true.shape)
def residuals(self, p, y, x):
a,b,c = p
err = y-(a*x**2 + b*x + c)
return err
def residuals_jacobian(self, _p, _y, x):
return -np.vstack([x**2, x, np.ones_like(x)]).T
def test_basic(self):
p0 = array([0,0,0])
params_fit, ier = leastsq(self.residuals, p0,
args=(self.y_meas, self.x))
assert_(ier in (1,2,3,4), 'solution not found (ier=%d)' % ier)
# low precision due to random
assert_array_almost_equal(params_fit, self.abc, decimal=2)
def test_basic_with_gradient(self):
p0 = array([0,0,0])
params_fit, ier = leastsq(self.residuals, p0,
args=(self.y_meas, self.x),
Dfun=self.residuals_jacobian)
assert_(ier in (1,2,3,4), 'solution not found (ier=%d)' % ier)
# low precision due to random
assert_array_almost_equal(params_fit, self.abc, decimal=2)
def test_full_output(self):
p0 = matrix([0,0,0])
full_output = leastsq(self.residuals, p0,
args=(self.y_meas, self.x),
full_output=True)
params_fit, cov_x, infodict, mesg, ier = full_output
assert_(ier in (1,2,3,4), 'solution not found: %s' % mesg)
def test_input_untouched(self):
p0 = array([0,0,0],dtype=float64)
p0_copy = array(p0, copy=True)
full_output = leastsq(self.residuals, p0,
args=(self.y_meas, self.x),
full_output=True)
params_fit, cov_x, infodict, mesg, ier = full_output
assert_(ier in (1,2,3,4), 'solution not found: %s' % mesg)
assert_array_equal(p0, p0_copy)
def test_wrong_shape_func_callable(self):
func = ReturnShape(1)
# x0 is a list of two elements, but func will return an array with
# length 1, so this should result in a TypeError.
x0 = [1.5, 2.0]
assert_raises(TypeError, optimize.leastsq, func, x0)
def test_wrong_shape_func_function(self):
# x0 is a list of two elements, but func will return an array with
# length 1, so this should result in a TypeError.
x0 = [1.5, 2.0]
assert_raises(TypeError, optimize.leastsq, dummy_func, x0, args=((1,),))
def test_wrong_shape_Dfun_callable(self):
func = ReturnShape(1)
deriv_func = ReturnShape((2,2))
assert_raises(TypeError, optimize.leastsq, func, x0=[0,1], Dfun=deriv_func)
def test_wrong_shape_Dfun_function(self):
func = lambda x: dummy_func(x, (2,))
deriv_func = lambda x: dummy_func(x, (3,3))
assert_raises(TypeError, optimize.leastsq, func, x0=[0,1], Dfun=deriv_func)
def test_float32(self):
# Regression test for gh-1447
def func(p,x,y):
q = p[0]*np.exp(-(x-p[1])**2/(2.0*p[2]**2))+p[3]
return q - y
x = np.array([1.475,1.429,1.409,1.419,1.455,1.519,1.472, 1.368,1.286,
1.231], dtype=np.float32)
y = np.array([0.0168,0.0193,0.0211,0.0202,0.0171,0.0151,0.0185,0.0258,
0.034,0.0396], dtype=np.float32)
p0 = np.array([1.0,1.0,1.0,1.0])
p1, success = optimize.leastsq(func, p0, args=(x,y))
assert_(success in [1,2,3,4])
assert_((func(p1,x,y)**2).sum() < 1e-4 * (func(p0,x,y)**2).sum())
def test_func_can_raise(self):
def func(*args):
raise ValueError('I raised')
with assert_raises(ValueError, match='I raised'):
optimize.leastsq(func, x0=[0])
def test_Dfun_can_raise(self):
func = lambda x: x - np.array([10])
def deriv_func(*args):
raise ValueError('I raised')
with assert_raises(ValueError, match='I raised'):
optimize.leastsq(func, x0=[0], Dfun=deriv_func)
def test_reentrant_func(self):
def func(*args):
self.test_basic()
return self.residuals(*args)
p0 = array([0,0,0])
params_fit, ier = leastsq(func, p0,
args=(self.y_meas, self.x))
assert_(ier in (1,2,3,4), 'solution not found (ier=%d)' % ier)
# low precision due to random
assert_array_almost_equal(params_fit, self.abc, decimal=2)
def test_reentrant_Dfun(self):
def deriv_func(*args):
self.test_basic()
return self.residuals_jacobian(*args)
p0 = array([0,0,0])
params_fit, ier = leastsq(self.residuals, p0,
args=(self.y_meas, self.x),
Dfun=deriv_func)
assert_(ier in (1,2,3,4), 'solution not found (ier=%d)' % ier)
# low precision due to random
assert_array_almost_equal(params_fit, self.abc, decimal=2)
def test_concurrent_no_gradient(self):
return sequence_parallel([self.test_basic] * 10)
def test_concurrent_with_gradient(self):
return sequence_parallel([self.test_basic_with_gradient] * 10)
class TestCurveFit(object):
def setup_method(self):
self.y = array([1.0, 3.2, 9.5, 13.7])
self.x = array([1.0, 2.0, 3.0, 4.0])
def test_one_argument(self):
def func(x,a):
return x**a
popt, pcov = curve_fit(func, self.x, self.y)
assert_(len(popt) == 1)
assert_(pcov.shape == (1,1))
assert_almost_equal(popt[0], 1.9149, decimal=4)
assert_almost_equal(pcov[0,0], 0.0016, decimal=4)
# Test if we get the same with full_output. Regression test for #1415.
res = curve_fit(func, self.x, self.y, full_output=1)
(popt2, pcov2, infodict, errmsg, ier) = res
assert_array_almost_equal(popt, popt2)
def test_two_argument(self):
def func(x, a, b):
return b*x**a
popt, pcov = curve_fit(func, self.x, self.y)
assert_(len(popt) == 2)
assert_(pcov.shape == (2,2))
assert_array_almost_equal(popt, [1.7989, 1.1642], decimal=4)
assert_array_almost_equal(pcov, [[0.0852, -0.1260], [-0.1260, 0.1912]],
decimal=4)
def test_func_is_classmethod(self):
class test_self(object):
"""This class tests if curve_fit passes the correct number of
arguments when the model function is a class instance method.
"""
def func(self, x, a, b):
return b * x**a
test_self_inst = test_self()
popt, pcov = curve_fit(test_self_inst.func, self.x, self.y)
assert_(pcov.shape == (2,2))
assert_array_almost_equal(popt, [1.7989, 1.1642], decimal=4)
assert_array_almost_equal(pcov, [[0.0852, -0.1260], [-0.1260, 0.1912]],
decimal=4)
def test_regression_2639(self):
# This test fails if epsfcn in leastsq is too large.
x = [574.14200000000005, 574.154, 574.16499999999996,
574.17700000000002, 574.18799999999999, 574.19899999999996,
574.21100000000001, 574.22199999999998, 574.23400000000004,
574.245]
y = [859.0, 997.0, 1699.0, 2604.0, 2013.0, 1964.0, 2435.0,
1550.0, 949.0, 841.0]
guess = [574.1861428571428, 574.2155714285715, 1302.0, 1302.0,
0.0035019999999983615, 859.0]
good = [5.74177150e+02, 5.74209188e+02, 1.74187044e+03, 1.58646166e+03,
1.0068462e-02, 8.57450661e+02]
def f_double_gauss(x, x0, x1, A0, A1, sigma, c):
return (A0*np.exp(-(x-x0)**2/(2.*sigma**2))
+ A1*np.exp(-(x-x1)**2/(2.*sigma**2)) + c)
popt, pcov = curve_fit(f_double_gauss, x, y, guess, maxfev=10000)
assert_allclose(popt, good, rtol=1e-5)
def test_pcov(self):
xdata = np.array([0, 1, 2, 3, 4, 5])
ydata = np.array([1, 1, 5, 7, 8, 12])
sigma = np.array([1, 2, 1, 2, 1, 2])
def f(x, a, b):
return a*x + b
for method in ['lm', 'trf', 'dogbox']:
popt, pcov = curve_fit(f, xdata, ydata, p0=[2, 0], sigma=sigma,
method=method)
perr_scaled = np.sqrt(np.diag(pcov))
assert_allclose(perr_scaled, [0.20659803, 0.57204404], rtol=1e-3)
popt, pcov = curve_fit(f, xdata, ydata, p0=[2, 0], sigma=3*sigma,
method=method)
perr_scaled = np.sqrt(np.diag(pcov))
assert_allclose(perr_scaled, [0.20659803, 0.57204404], rtol=1e-3)
popt, pcov = curve_fit(f, xdata, ydata, p0=[2, 0], sigma=sigma,
absolute_sigma=True, method=method)
perr = np.sqrt(np.diag(pcov))
assert_allclose(perr, [0.30714756, 0.85045308], rtol=1e-3)
popt, pcov = curve_fit(f, xdata, ydata, p0=[2, 0], sigma=3*sigma,
absolute_sigma=True, method=method)
perr = np.sqrt(np.diag(pcov))
assert_allclose(perr, [3*0.30714756, 3*0.85045308], rtol=1e-3)
# infinite variances
def f_flat(x, a, b):
return a*x
pcov_expected = np.array([np.inf]*4).reshape(2, 2)
with suppress_warnings() as sup:
sup.filter(OptimizeWarning,
"Covariance of the parameters could not be estimated")
popt, pcov = curve_fit(f_flat, xdata, ydata, p0=[2, 0], sigma=sigma)
popt1, pcov1 = curve_fit(f, xdata[:2], ydata[:2], p0=[2, 0])
assert_(pcov.shape == (2, 2))
assert_array_equal(pcov, pcov_expected)
assert_(pcov1.shape == (2, 2))
assert_array_equal(pcov1, pcov_expected)
def test_array_like(self):
# Test sequence input. Regression test for gh-3037.
def f_linear(x, a, b):
return a*x + b
x = [1, 2, 3, 4]
y = [3, 5, 7, 9]
assert_allclose(curve_fit(f_linear, x, y)[0], [2, 1], atol=1e-10)
def test_indeterminate_covariance(self):
# Test that a warning is returned when pcov is indeterminate
xdata = np.array([1, 2, 3, 4, 5, 6])
ydata = np.array([1, 2, 3, 4, 5.5, 6])
_assert_warns(OptimizeWarning, curve_fit,
lambda x, a, b: a*x, xdata, ydata)
def test_NaN_handling(self):
# Test for correct handling of NaNs in input data: gh-3422
# create input with NaNs
xdata = np.array([1, np.nan, 3])
ydata = np.array([1, 2, 3])
assert_raises(ValueError, curve_fit,
lambda x, a, b: a*x + b, xdata, ydata)
assert_raises(ValueError, curve_fit,
lambda x, a, b: a*x + b, ydata, xdata)
assert_raises(ValueError, curve_fit, lambda x, a, b: a*x + b,
xdata, ydata, **{"check_finite": True})
def test_method_argument(self):
def f(x, a, b):
return a * np.exp(-b*x)
xdata = np.linspace(0, 1, 11)
ydata = f(xdata, 2., 2.)
for method in ['trf', 'dogbox', 'lm', None]:
popt, pcov = curve_fit(f, xdata, ydata, method=method)
assert_allclose(popt, [2., 2.])
assert_raises(ValueError, curve_fit, f, xdata, ydata, method='unknown')
def test_bounds(self):
def f(x, a, b):
return a * np.exp(-b*x)
xdata = np.linspace(0, 1, 11)
ydata = f(xdata, 2., 2.)
# The minimum w/out bounds is at [2., 2.],
# and with bounds it's at [1.5, smth].
bounds = ([1., 0], [1.5, 3.])
for method in [None, 'trf', 'dogbox']:
popt, pcov = curve_fit(f, xdata, ydata, bounds=bounds,
method=method)
assert_allclose(popt[0], 1.5)
# With bounds, the starting estimate is feasible.
popt, pcov = curve_fit(f, xdata, ydata, method='trf',
bounds=([0., 0], [0.6, np.inf]))
assert_allclose(popt[0], 0.6)
# method='lm' doesn't support bounds.
assert_raises(ValueError, curve_fit, f, xdata, ydata, bounds=bounds,
method='lm')
def test_bounds_p0(self):
# This test is for issue #5719. The problem was that an initial guess
# was ignored when 'trf' or 'dogbox' methods were invoked.
def f(x, a):
return np.sin(x + a)
xdata = np.linspace(-2*np.pi, 2*np.pi, 40)
ydata = np.sin(xdata)
bounds = (-3 * np.pi, 3 * np.pi)
for method in ['trf', 'dogbox']:
popt_1, _ = curve_fit(f, xdata, ydata, p0=2.1*np.pi)
popt_2, _ = curve_fit(f, xdata, ydata, p0=2.1*np.pi,
bounds=bounds, method=method)
# If the initial guess is ignored, then popt_2 would be close 0.
assert_allclose(popt_1, popt_2)
def test_jac(self):
# Test that Jacobian callable is handled correctly and
# weighted if sigma is provided.
def f(x, a, b):
return a * np.exp(-b*x)
def jac(x, a, b):
e = np.exp(-b*x)
return np.vstack((e, -a * x * e)).T
xdata = np.linspace(0, 1, 11)
ydata = f(xdata, 2., 2.)
# Test numerical options for least_squares backend.
for method in ['trf', 'dogbox']:
for scheme in ['2-point', '3-point', 'cs']:
popt, pcov = curve_fit(f, xdata, ydata, jac=scheme,
method=method)
assert_allclose(popt, [2, 2])
# Test the analytic option.
for method in ['lm', 'trf', 'dogbox']:
popt, pcov = curve_fit(f, xdata, ydata, method=method, jac=jac)
assert_allclose(popt, [2, 2])
# Now add an outlier and provide sigma.
ydata[5] = 100
sigma = np.ones(xdata.shape[0])
sigma[5] = 200
for method in ['lm', 'trf', 'dogbox']:
popt, pcov = curve_fit(f, xdata, ydata, sigma=sigma, method=method,
jac=jac)
# Still the optimization process is influenced somehow,
# have to set rtol=1e-3.
assert_allclose(popt, [2, 2], rtol=1e-3)
def test_maxfev_and_bounds(self):
# gh-6340: with no bounds, curve_fit accepts parameter maxfev (via leastsq)
# but with bounds, the parameter is `max_nfev` (via least_squares)
x = np.arange(0, 10)
y = 2*x
popt1, _ = curve_fit(lambda x,p: p*x, x, y, bounds=(0, 3), maxfev=100)
popt2, _ = curve_fit(lambda x,p: p*x, x, y, bounds=(0, 3), max_nfev=100)
assert_allclose(popt1, 2, atol=1e-14)
assert_allclose(popt2, 2, atol=1e-14)
def test_curvefit_simplecovariance(self):
def func(x, a, b):
return a * np.exp(-b*x)
def jac(x, a, b):
e = np.exp(-b*x)
return np.vstack((e, -a * x * e)).T
np.random.seed(0)
xdata = np.linspace(0, 4, 50)
y = func(xdata, 2.5, 1.3)
ydata = y + 0.2 * np.random.normal(size=len(xdata))
sigma = np.zeros(len(xdata)) + 0.2
covar = np.diag(sigma**2)
for jac1, jac2 in [(jac, jac), (None, None)]:
for absolute_sigma in [False, True]:
popt1, pcov1 = curve_fit(func, xdata, ydata, sigma=sigma,
jac=jac1, absolute_sigma=absolute_sigma)
popt2, pcov2 = curve_fit(func, xdata, ydata, sigma=covar,
jac=jac2, absolute_sigma=absolute_sigma)
assert_allclose(popt1, popt2, atol=1e-14)
assert_allclose(pcov1, pcov2, atol=1e-14)
def test_curvefit_covariance(self):
def funcp(x, a, b):
rotn = np.array([[1./np.sqrt(2), -1./np.sqrt(2), 0], [1./np.sqrt(2), 1./np.sqrt(2), 0], [0, 0, 1.0]])
return rotn.dot(a * np.exp(-b*x))
def jacp(x, a, b):
rotn = np.array([[1./np.sqrt(2), -1./np.sqrt(2), 0], [1./np.sqrt(2), 1./np.sqrt(2), 0], [0, 0, 1.0]])
e = np.exp(-b*x)
return rotn.dot(np.vstack((e, -a * x * e)).T)
def func(x, a, b):
return a * np.exp(-b*x)
def jac(x, a, b):
e = np.exp(-b*x)
return np.vstack((e, -a * x * e)).T
np.random.seed(0)
xdata = np.arange(1, 4)
y = func(xdata, 2.5, 1.0)
ydata = y + 0.2 * np.random.normal(size=len(xdata))
sigma = np.zeros(len(xdata)) + 0.2
covar = np.diag(sigma**2)
# Get a rotation matrix, and obtain ydatap = R ydata
# Chisq = ydata^T C^{-1} ydata
# = ydata^T R^T R C^{-1} R^T R ydata
# = ydatap^T Cp^{-1} ydatap
# Cp^{-1} = R C^{-1} R^T
# Cp = R C R^T, since R^-1 = R^T
rotn = np.array([[1./np.sqrt(2), -1./np.sqrt(2), 0], [1./np.sqrt(2), 1./np.sqrt(2), 0], [0, 0, 1.0]])
ydatap = rotn.dot(ydata)
covarp = rotn.dot(covar).dot(rotn.T)
for jac1, jac2 in [(jac, jacp), (None, None)]:
for absolute_sigma in [False, True]:
popt1, pcov1 = curve_fit(func, xdata, ydata, sigma=sigma,
jac=jac1, absolute_sigma=absolute_sigma)
popt2, pcov2 = curve_fit(funcp, xdata, ydatap, sigma=covarp,
jac=jac2, absolute_sigma=absolute_sigma)
assert_allclose(popt1, popt2, atol=1e-14)
assert_allclose(pcov1, pcov2, atol=1e-14)
class TestFixedPoint(object):
def test_scalar_trivial(self):
# f(x) = 2x; fixed point should be x=0
def func(x):
return 2.0*x
x0 = 1.0
x = fixed_point(func, x0)
assert_almost_equal(x, 0.0)
def test_scalar_basic1(self):
# f(x) = x**2; x0=1.05; fixed point should be x=1
def func(x):
return x**2
x0 = 1.05
x = fixed_point(func, x0)
assert_almost_equal(x, 1.0)
def test_scalar_basic2(self):
# f(x) = x**0.5; x0=1.05; fixed point should be x=1
def func(x):
return x**0.5
x0 = 1.05
x = fixed_point(func, x0)
assert_almost_equal(x, 1.0)
def test_array_trivial(self):
def func(x):
return 2.0*x
x0 = [0.3, 0.15]
olderr = np.seterr(all='ignore')
try:
x = fixed_point(func, x0)
finally:
np.seterr(**olderr)
assert_almost_equal(x, [0.0, 0.0])
def test_array_basic1(self):
# f(x) = c * x**2; fixed point should be x=1/c
def func(x, c):
return c * x**2
c = array([0.75, 1.0, 1.25])
x0 = [1.1, 1.15, 0.9]
olderr = np.seterr(all='ignore')
try:
x = fixed_point(func, x0, args=(c,))
finally:
np.seterr(**olderr)
assert_almost_equal(x, 1.0/c)
def test_array_basic2(self):
# f(x) = c * x**0.5; fixed point should be x=c**2
def func(x, c):
return c * x**0.5
c = array([0.75, 1.0, 1.25])
x0 = [0.8, 1.1, 1.1]
x = fixed_point(func, x0, args=(c,))
assert_almost_equal(x, c**2)
def test_lambertw(self):
# python-list/2010-December/594592.html
xxroot = fixed_point(lambda xx: np.exp(-2.0*xx)/2.0, 1.0,
args=(), xtol=1e-12, maxiter=500)
assert_allclose(xxroot, np.exp(-2.0*xxroot)/2.0)
assert_allclose(xxroot, lambertw(1)/2)
def test_no_acceleration(self):
# github issue 5460
ks = 2
kl = 6
m = 1.3
n0 = 1.001
i0 = ((m-1)/m)*(kl/ks/m)**(1/(m-1))
def func(n):
return np.log(kl/ks/n) / np.log((i0*n/(n - 1))) + 1
n = fixed_point(func, n0, method='iteration')
assert_allclose(n, m)
@@ -0,0 +1,36 @@
""" Unit tests for nonnegative least squares
Author: Uwe Schmitt
Sep 2008
"""
from __future__ import division, print_function, absolute_import
import numpy as np
from numpy.testing import assert_
from pytest import raises as assert_raises
from scipy.optimize import nnls
from numpy import arange, dot
from numpy.linalg import norm
class TestNNLS(object):
def test_nnls(self):
a = arange(25.0).reshape(-1,5)
x = arange(5.0)
y = dot(a,x)
x, res = nnls(a,y)
assert_(res < 1e-7)
assert_(norm(dot(a,x)-y) < 1e-7)
def test_maxiter(self):
# test that maxiter argument does stop iterations
# NB: did not manage to find a test case where the default value
# of maxiter is not sufficient, so use a too-small value
rndm = np.random.RandomState(1234)
a = rndm.uniform(size=(100, 100))
b = rndm.uniform(size=100)
with assert_raises(RuntimeError):
nnls(a, b, maxiter=1)
@@ -0,0 +1,448 @@
""" Unit tests for nonlinear solvers
Author: Ondrej Certik
May 2007
"""
from __future__ import division, print_function, absolute_import
from numpy.testing import assert_
import pytest
from scipy._lib.six import xrange
from scipy.optimize import nonlin, root
from numpy import matrix, diag, dot
from numpy.linalg import inv
import numpy as np
from .test_minpack import pressure_network
SOLVERS = {'anderson': nonlin.anderson, 'diagbroyden': nonlin.diagbroyden,
'linearmixing': nonlin.linearmixing, 'excitingmixing': nonlin.excitingmixing,
'broyden1': nonlin.broyden1, 'broyden2': nonlin.broyden2,
'krylov': nonlin.newton_krylov}
MUST_WORK = {'anderson': nonlin.anderson, 'broyden1': nonlin.broyden1,
'broyden2': nonlin.broyden2, 'krylov': nonlin.newton_krylov}
#-------------------------------------------------------------------------------
# Test problems
#-------------------------------------------------------------------------------
def F(x):
x = np.asmatrix(x).T
d = matrix(diag([3,2,1.5,1,0.5]))
c = 0.01
f = -d*x - c*float(x.T*x)*x
return f
F.xin = [1,1,1,1,1]
F.KNOWN_BAD = {}
def F2(x):
return x
F2.xin = [1,2,3,4,5,6]
F2.KNOWN_BAD = {'linearmixing': nonlin.linearmixing,
'excitingmixing': nonlin.excitingmixing}
def F2_lucky(x):
return x
F2_lucky.xin = [0,0,0,0,0,0]
F2_lucky.KNOWN_BAD = {}
def F3(x):
A = np.mat('-2 1 0; 1 -2 1; 0 1 -2')
b = np.mat('1 2 3')
return np.dot(A, x) - b
F3.xin = [1,2,3]
F3.KNOWN_BAD = {}
def F4_powell(x):
A = 1e4
return [A*x[0]*x[1] - 1, np.exp(-x[0]) + np.exp(-x[1]) - (1 + 1/A)]
F4_powell.xin = [-1, -2]
F4_powell.KNOWN_BAD = {'linearmixing': nonlin.linearmixing,
'excitingmixing': nonlin.excitingmixing,
'diagbroyden': nonlin.diagbroyden}
def F5(x):
return pressure_network(x, 4, np.array([.5, .5, .5, .5]))
F5.xin = [2., 0, 2, 0]
F5.KNOWN_BAD = {'excitingmixing': nonlin.excitingmixing,
'linearmixing': nonlin.linearmixing,
'diagbroyden': nonlin.diagbroyden}
def F6(x):
x1, x2 = x
J0 = np.array([[-4.256, 14.7],
[0.8394989, 0.59964207]])
v = np.array([(x1 + 3) * (x2**5 - 7) + 3*6,
np.sin(x2 * np.exp(x1) - 1)])
return -np.linalg.solve(J0, v)
F6.xin = [-0.5, 1.4]
F6.KNOWN_BAD = {'excitingmixing': nonlin.excitingmixing,
'linearmixing': nonlin.linearmixing,
'diagbroyden': nonlin.diagbroyden}
#-------------------------------------------------------------------------------
# Tests
#-------------------------------------------------------------------------------
class TestNonlin(object):
"""
Check the Broyden methods for a few test problems.
broyden1, broyden2, and newton_krylov must succeed for
all functions. Some of the others don't -- tests in KNOWN_BAD are skipped.
"""
def _check_nonlin_func(self, f, func, f_tol=1e-2):
x = func(f, f.xin, f_tol=f_tol, maxiter=200, verbose=0)
assert_(np.absolute(f(x)).max() < f_tol)
def _check_root(self, f, method, f_tol=1e-2):
res = root(f, f.xin, method=method,
options={'ftol': f_tol, 'maxiter': 200, 'disp': 0})
assert_(np.absolute(res.fun).max() < f_tol)
@pytest.mark.xfail
def _check_func_fail(self, *a, **kw):
pass
def test_problem_nonlin(self):
for f in [F, F2, F2_lucky, F3, F4_powell, F5, F6]:
for func in SOLVERS.values():
if func in f.KNOWN_BAD.values():
if func in MUST_WORK.values():
self._check_func_fail(f, func)
continue
self._check_nonlin_func(f, func)
def test_tol_norm_called(self):
# Check that supplying tol_norm keyword to nonlin_solve works
self._tol_norm_used = False
def local_norm_func(x):
self._tol_norm_used = True
return np.absolute(x).max()
nonlin.newton_krylov(F, F.xin, f_tol=1e-2, maxiter=200, verbose=0,
tol_norm=local_norm_func)
assert_(self._tol_norm_used)
def test_problem_root(self):
for f in [F, F2, F2_lucky, F3, F4_powell, F5, F6]:
for meth in SOLVERS:
if meth in f.KNOWN_BAD:
if meth in MUST_WORK:
self._check_func_fail(f, meth)
continue
self._check_root(f, meth)
class TestSecant(object):
"""Check that some Jacobian approximations satisfy the secant condition"""
xs = [np.array([1,2,3,4,5], float),
np.array([2,3,4,5,1], float),
np.array([3,4,5,1,2], float),
np.array([4,5,1,2,3], float),
np.array([9,1,9,1,3], float),
np.array([0,1,9,1,3], float),
np.array([5,5,7,1,1], float),
np.array([1,2,7,5,1], float),]
fs = [x**2 - 1 for x in xs]
def _check_secant(self, jac_cls, npoints=1, **kw):
"""
Check that the given Jacobian approximation satisfies secant
conditions for last `npoints` points.
"""
jac = jac_cls(**kw)
jac.setup(self.xs[0], self.fs[0], None)
for j, (x, f) in enumerate(zip(self.xs[1:], self.fs[1:])):
jac.update(x, f)
for k in xrange(min(npoints, j+1)):
dx = self.xs[j-k+1] - self.xs[j-k]
df = self.fs[j-k+1] - self.fs[j-k]
assert_(np.allclose(dx, jac.solve(df)))
# Check that the `npoints` secant bound is strict
if j >= npoints:
dx = self.xs[j-npoints+1] - self.xs[j-npoints]
df = self.fs[j-npoints+1] - self.fs[j-npoints]
assert_(not np.allclose(dx, jac.solve(df)))
def test_broyden1(self):
self._check_secant(nonlin.BroydenFirst)
def test_broyden2(self):
self._check_secant(nonlin.BroydenSecond)
def test_broyden1_update(self):
# Check that BroydenFirst update works as for a dense matrix
jac = nonlin.BroydenFirst(alpha=0.1)
jac.setup(self.xs[0], self.fs[0], None)
B = np.identity(5) * (-1/0.1)
for last_j, (x, f) in enumerate(zip(self.xs[1:], self.fs[1:])):
df = f - self.fs[last_j]
dx = x - self.xs[last_j]
B += (df - dot(B, dx))[:,None] * dx[None,:] / dot(dx, dx)
jac.update(x, f)
assert_(np.allclose(jac.todense(), B, rtol=1e-10, atol=1e-13))
def test_broyden2_update(self):
# Check that BroydenSecond update works as for a dense matrix
jac = nonlin.BroydenSecond(alpha=0.1)
jac.setup(self.xs[0], self.fs[0], None)
H = np.identity(5) * (-0.1)
for last_j, (x, f) in enumerate(zip(self.xs[1:], self.fs[1:])):
df = f - self.fs[last_j]
dx = x - self.xs[last_j]
H += (dx - dot(H, df))[:,None] * df[None,:] / dot(df, df)
jac.update(x, f)
assert_(np.allclose(jac.todense(), inv(H), rtol=1e-10, atol=1e-13))
def test_anderson(self):
# Anderson mixing (with w0=0) satisfies secant conditions
# for the last M iterates, see [Ey]_
#
# .. [Ey] V. Eyert, J. Comp. Phys., 124, 271 (1996).
self._check_secant(nonlin.Anderson, M=3, w0=0, npoints=3)
class TestLinear(object):
"""Solve a linear equation;
some methods find the exact solution in a finite number of steps"""
def _check(self, jac, N, maxiter, complex=False, **kw):
np.random.seed(123)
A = np.random.randn(N, N)
if complex:
A = A + 1j*np.random.randn(N, N)
b = np.random.randn(N)
if complex:
b = b + 1j*np.random.randn(N)
def func(x):
return dot(A, x) - b
sol = nonlin.nonlin_solve(func, np.zeros(N), jac, maxiter=maxiter,
f_tol=1e-6, line_search=None, verbose=0)
assert_(np.allclose(dot(A, sol), b, atol=1e-6))
def test_broyden1(self):
# Broyden methods solve linear systems exactly in 2*N steps
self._check(nonlin.BroydenFirst(alpha=1.0), 20, 41, False)
self._check(nonlin.BroydenFirst(alpha=1.0), 20, 41, True)
def test_broyden2(self):
# Broyden methods solve linear systems exactly in 2*N steps
self._check(nonlin.BroydenSecond(alpha=1.0), 20, 41, False)
self._check(nonlin.BroydenSecond(alpha=1.0), 20, 41, True)
def test_anderson(self):
# Anderson is rather similar to Broyden, if given enough storage space
self._check(nonlin.Anderson(M=50, alpha=1.0), 20, 29, False)
self._check(nonlin.Anderson(M=50, alpha=1.0), 20, 29, True)
def test_krylov(self):
# Krylov methods solve linear systems exactly in N inner steps
self._check(nonlin.KrylovJacobian, 20, 2, False, inner_m=10)
self._check(nonlin.KrylovJacobian, 20, 2, True, inner_m=10)
class TestJacobianDotSolve(object):
"""Check that solve/dot methods in Jacobian approximations are consistent"""
def _func(self, x):
return x**2 - 1 + np.dot(self.A, x)
def _check_dot(self, jac_cls, complex=False, tol=1e-6, **kw):
np.random.seed(123)
N = 7
def rand(*a):
q = np.random.rand(*a)
if complex:
q = q + 1j*np.random.rand(*a)
return q
def assert_close(a, b, msg):
d = abs(a - b).max()
f = tol + abs(b).max()*tol
if d > f:
raise AssertionError('%s: err %g' % (msg, d))
self.A = rand(N, N)
# initialize
x0 = np.random.rand(N)
jac = jac_cls(**kw)
jac.setup(x0, self._func(x0), self._func)
# check consistency
for k in xrange(2*N):
v = rand(N)
if hasattr(jac, '__array__'):
Jd = np.array(jac)
if hasattr(jac, 'solve'):
Gv = jac.solve(v)
Gv2 = np.linalg.solve(Jd, v)
assert_close(Gv, Gv2, 'solve vs array')
if hasattr(jac, 'rsolve'):
Gv = jac.rsolve(v)
Gv2 = np.linalg.solve(Jd.T.conj(), v)
assert_close(Gv, Gv2, 'rsolve vs array')
if hasattr(jac, 'matvec'):
Jv = jac.matvec(v)
Jv2 = np.dot(Jd, v)
assert_close(Jv, Jv2, 'dot vs array')
if hasattr(jac, 'rmatvec'):
Jv = jac.rmatvec(v)
Jv2 = np.dot(Jd.T.conj(), v)
assert_close(Jv, Jv2, 'rmatvec vs array')
if hasattr(jac, 'matvec') and hasattr(jac, 'solve'):
Jv = jac.matvec(v)
Jv2 = jac.solve(jac.matvec(Jv))
assert_close(Jv, Jv2, 'dot vs solve')
if hasattr(jac, 'rmatvec') and hasattr(jac, 'rsolve'):
Jv = jac.rmatvec(v)
Jv2 = jac.rmatvec(jac.rsolve(Jv))
assert_close(Jv, Jv2, 'rmatvec vs rsolve')
x = rand(N)
jac.update(x, self._func(x))
def test_broyden1(self):
self._check_dot(nonlin.BroydenFirst, complex=False)
self._check_dot(nonlin.BroydenFirst, complex=True)
def test_broyden2(self):
self._check_dot(nonlin.BroydenSecond, complex=False)
self._check_dot(nonlin.BroydenSecond, complex=True)
def test_anderson(self):
self._check_dot(nonlin.Anderson, complex=False)
self._check_dot(nonlin.Anderson, complex=True)
def test_diagbroyden(self):
self._check_dot(nonlin.DiagBroyden, complex=False)
self._check_dot(nonlin.DiagBroyden, complex=True)
def test_linearmixing(self):
self._check_dot(nonlin.LinearMixing, complex=False)
self._check_dot(nonlin.LinearMixing, complex=True)
def test_excitingmixing(self):
self._check_dot(nonlin.ExcitingMixing, complex=False)
self._check_dot(nonlin.ExcitingMixing, complex=True)
def test_krylov(self):
self._check_dot(nonlin.KrylovJacobian, complex=False, tol=1e-3)
self._check_dot(nonlin.KrylovJacobian, complex=True, tol=1e-3)
class TestNonlinOldTests(object):
""" Test case for a simple constrained entropy maximization problem
(the machine translation example of Berger et al in
Computational Linguistics, vol 22, num 1, pp 39--72, 1996.)
"""
def test_broyden1(self):
x = nonlin.broyden1(F,F.xin,iter=12,alpha=1)
assert_(nonlin.norm(x) < 1e-9)
assert_(nonlin.norm(F(x)) < 1e-9)
def test_broyden2(self):
x = nonlin.broyden2(F,F.xin,iter=12,alpha=1)
assert_(nonlin.norm(x) < 1e-9)
assert_(nonlin.norm(F(x)) < 1e-9)
def test_anderson(self):
x = nonlin.anderson(F,F.xin,iter=12,alpha=0.03,M=5)
assert_(nonlin.norm(x) < 0.33)
def test_linearmixing(self):
x = nonlin.linearmixing(F,F.xin,iter=60,alpha=0.5)
assert_(nonlin.norm(x) < 1e-7)
assert_(nonlin.norm(F(x)) < 1e-7)
def test_exciting(self):
x = nonlin.excitingmixing(F,F.xin,iter=20,alpha=0.5)
assert_(nonlin.norm(x) < 1e-5)
assert_(nonlin.norm(F(x)) < 1e-5)
def test_diagbroyden(self):
x = nonlin.diagbroyden(F,F.xin,iter=11,alpha=1)
assert_(nonlin.norm(x) < 1e-8)
assert_(nonlin.norm(F(x)) < 1e-8)
def test_root_broyden1(self):
res = root(F, F.xin, method='broyden1',
options={'nit': 12, 'jac_options': {'alpha': 1}})
assert_(nonlin.norm(res.x) < 1e-9)
assert_(nonlin.norm(res.fun) < 1e-9)
def test_root_broyden2(self):
res = root(F, F.xin, method='broyden2',
options={'nit': 12, 'jac_options': {'alpha': 1}})
assert_(nonlin.norm(res.x) < 1e-9)
assert_(nonlin.norm(res.fun) < 1e-9)
def test_root_anderson(self):
res = root(F, F.xin, method='anderson',
options={'nit': 12,
'jac_options': {'alpha': 0.03, 'M': 5}})
assert_(nonlin.norm(res.x) < 0.33)
def test_root_linearmixing(self):
res = root(F, F.xin, method='linearmixing',
options={'nit': 60,
'jac_options': {'alpha': 0.5}})
assert_(nonlin.norm(res.x) < 1e-7)
assert_(nonlin.norm(res.fun) < 1e-7)
def test_root_excitingmixing(self):
res = root(F, F.xin, method='excitingmixing',
options={'nit': 20,
'jac_options': {'alpha': 0.5}})
assert_(nonlin.norm(res.x) < 1e-5)
assert_(nonlin.norm(res.fun) < 1e-5)
def test_root_diagbroyden(self):
res = root(F, F.xin, method='diagbroyden',
options={'nit': 11,
'jac_options': {'alpha': 1}})
assert_(nonlin.norm(res.x) < 1e-8)
assert_(nonlin.norm(res.fun) < 1e-8)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
"""Regression tests for optimize.
"""
from __future__ import division, print_function, absolute_import
import numpy as np
from numpy.testing import assert_almost_equal
from pytest import raises as assert_raises
import scipy.optimize
class TestRegression(object):
def test_newton_x0_is_0(self):
# Regression test for gh-1601
tgt = 1
res = scipy.optimize.newton(lambda x: x - 1, 0)
assert_almost_equal(res, tgt)
def test_newton_integers(self):
# Regression test for gh-1741
root = scipy.optimize.newton(lambda x: x**2 - 1, x0=2,
fprime=lambda x: 2*x)
assert_almost_equal(root, 1.0)
def test_lmdif_errmsg(self):
# This shouldn't cause a crash on Python 3
class SomeError(Exception):
pass
counter = [0]
def func(x):
counter[0] += 1
if counter[0] < 3:
return x**2 - np.array([9, 10, 11])
else:
raise SomeError()
assert_raises(SomeError,
scipy.optimize.leastsq,
func, [1, 2, 3])
@@ -0,0 +1,512 @@
"""
Unit test for SLSQP optimization.
"""
from __future__ import division, print_function, absolute_import
import pytest
from numpy.testing import (assert_, assert_array_almost_equal,
assert_allclose, assert_equal)
from pytest import raises as assert_raises
import numpy as np
from scipy.optimize import fmin_slsqp, minimize, NonlinearConstraint, Bounds
class MyCallBack(object):
"""pass a custom callback function
This makes sure it's being used.
"""
def __init__(self):
self.been_called = False
self.ncalls = 0
def __call__(self, x):
self.been_called = True
self.ncalls += 1
class TestSLSQP(object):
"""
Test SLSQP algorithm using Example 14.4 from Numerical Methods for
Engineers by Steven Chapra and Raymond Canale.
This example maximizes the function f(x) = 2*x*y + 2*x - x**2 - 2*y**2,
which has a maximum at x=2, y=1.
"""
def setup_method(self):
self.opts = {'disp': False}
def fun(self, d, sign=1.0):
"""
Arguments:
d - A list of two elements, where d[0] represents x and d[1] represents y
in the following equation.
sign - A multiplier for f. Since we want to optimize it, and the scipy
optimizers can only minimize functions, we need to multiply it by
-1 to achieve the desired solution
Returns:
2*x*y + 2*x - x**2 - 2*y**2
"""
x = d[0]
y = d[1]
return sign*(2*x*y + 2*x - x**2 - 2*y**2)
def jac(self, d, sign=1.0):
"""
This is the derivative of fun, returning a numpy array
representing df/dx and df/dy.
"""
x = d[0]
y = d[1]
dfdx = sign*(-2*x + 2*y + 2)
dfdy = sign*(2*x - 4*y)
return np.array([dfdx, dfdy], float)
def fun_and_jac(self, d, sign=1.0):
return self.fun(d, sign), self.jac(d, sign)
def f_eqcon(self, x, sign=1.0):
""" Equality constraint """
return np.array([x[0] - x[1]])
def fprime_eqcon(self, x, sign=1.0):
""" Equality constraint, derivative """
return np.array([[1, -1]])
def f_eqcon_scalar(self, x, sign=1.0):
""" Scalar equality constraint """
return self.f_eqcon(x, sign)[0]
def fprime_eqcon_scalar(self, x, sign=1.0):
""" Scalar equality constraint, derivative """
return self.fprime_eqcon(x, sign)[0].tolist()
def f_ieqcon(self, x, sign=1.0):
""" Inequality constraint """
return np.array([x[0] - x[1] - 1.0])
def fprime_ieqcon(self, x, sign=1.0):
""" Inequality constraint, derivative """
return np.array([[1, -1]])
def f_ieqcon2(self, x):
""" Vector inequality constraint """
return np.asarray(x)
def fprime_ieqcon2(self, x):
""" Vector inequality constraint, derivative """
return np.identity(x.shape[0])
# minimize
def test_minimize_unbounded_approximated(self):
# Minimize, method='SLSQP': unbounded, approximated jacobian.
res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
method='SLSQP', options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [2, 1])
def test_minimize_unbounded_given(self):
# Minimize, method='SLSQP': unbounded, given jacobian.
res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
jac=self.jac, method='SLSQP', options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [2, 1])
def test_minimize_bounded_approximated(self):
# Minimize, method='SLSQP': bounded, approximated jacobian.
with np.errstate(invalid='ignore'):
res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
bounds=((2.5, None), (None, 0.5)),
method='SLSQP', options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [2.5, 0.5])
assert_(2.5 <= res.x[0])
assert_(res.x[1] <= 0.5)
def test_minimize_unbounded_combined(self):
# Minimize, method='SLSQP': unbounded, combined function and jacobian.
res = minimize(self.fun_and_jac, [-1.0, 1.0], args=(-1.0, ),
jac=True, method='SLSQP', options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [2, 1])
def test_minimize_equality_approximated(self):
# Minimize with method='SLSQP': equality constraint, approx. jacobian.
res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
constraints={'type': 'eq',
'fun': self.f_eqcon,
'args': (-1.0, )},
method='SLSQP', options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [1, 1])
def test_minimize_equality_given(self):
# Minimize with method='SLSQP': equality constraint, given jacobian.
res = minimize(self.fun, [-1.0, 1.0], jac=self.jac,
method='SLSQP', args=(-1.0,),
constraints={'type': 'eq', 'fun':self.f_eqcon,
'args': (-1.0, )},
options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [1, 1])
def test_minimize_equality_given2(self):
# Minimize with method='SLSQP': equality constraint, given jacobian
# for fun and const.
res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
jac=self.jac, args=(-1.0,),
constraints={'type': 'eq',
'fun': self.f_eqcon,
'args': (-1.0, ),
'jac': self.fprime_eqcon},
options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [1, 1])
def test_minimize_equality_given_cons_scalar(self):
# Minimize with method='SLSQP': scalar equality constraint, given
# jacobian for fun and const.
res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
jac=self.jac, args=(-1.0,),
constraints={'type': 'eq',
'fun': self.f_eqcon_scalar,
'args': (-1.0, ),
'jac': self.fprime_eqcon_scalar},
options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [1, 1])
def test_minimize_inequality_given(self):
# Minimize with method='SLSQP': inequality constraint, given jacobian.
res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
jac=self.jac, args=(-1.0, ),
constraints={'type': 'ineq',
'fun': self.f_ieqcon,
'args': (-1.0, )},
options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [2, 1], atol=1e-3)
def test_minimize_inequality_given_vector_constraints(self):
# Minimize with method='SLSQP': vector inequality constraint, given
# jacobian.
res = minimize(self.fun, [-1.0, 1.0], jac=self.jac,
method='SLSQP', args=(-1.0,),
constraints={'type': 'ineq',
'fun': self.f_ieqcon2,
'jac': self.fprime_ieqcon2},
options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [2, 1])
def test_minimize_bound_equality_given2(self):
# Minimize with method='SLSQP': bounds, eq. const., given jac. for
# fun. and const.
res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
jac=self.jac, args=(-1.0, ),
bounds=[(-0.8, 1.), (-1, 0.8)],
constraints={'type': 'eq',
'fun': self.f_eqcon,
'args': (-1.0, ),
'jac': self.fprime_eqcon},
options=self.opts)
assert_(res['success'], res['message'])
assert_allclose(res.x, [0.8, 0.8], atol=1e-3)
assert_(-0.8 <= res.x[0] <= 1)
assert_(-1 <= res.x[1] <= 0.8)
# fmin_slsqp
def test_unbounded_approximated(self):
# SLSQP: unbounded, approximated jacobian.
res = fmin_slsqp(self.fun, [-1.0, 1.0], args=(-1.0, ),
iprint = 0, full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [2, 1])
def test_unbounded_given(self):
# SLSQP: unbounded, given jacobian.
res = fmin_slsqp(self.fun, [-1.0, 1.0], args=(-1.0, ),
fprime = self.jac, iprint = 0,
full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [2, 1])
def test_equality_approximated(self):
# SLSQP: equality constraint, approximated jacobian.
res = fmin_slsqp(self.fun,[-1.0,1.0], args=(-1.0,),
eqcons = [self.f_eqcon],
iprint = 0, full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [1, 1])
def test_equality_given(self):
# SLSQP: equality constraint, given jacobian.
res = fmin_slsqp(self.fun, [-1.0, 1.0],
fprime=self.jac, args=(-1.0,),
eqcons = [self.f_eqcon], iprint = 0,
full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [1, 1])
def test_equality_given2(self):
# SLSQP: equality constraint, given jacobian for fun and const.
res = fmin_slsqp(self.fun, [-1.0, 1.0],
fprime=self.jac, args=(-1.0,),
f_eqcons = self.f_eqcon,
fprime_eqcons = self.fprime_eqcon,
iprint = 0,
full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [1, 1])
def test_inequality_given(self):
# SLSQP: inequality constraint, given jacobian.
res = fmin_slsqp(self.fun, [-1.0, 1.0],
fprime=self.jac, args=(-1.0, ),
ieqcons = [self.f_ieqcon],
iprint = 0, full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [2, 1], decimal=3)
def test_bound_equality_given2(self):
# SLSQP: bounds, eq. const., given jac. for fun. and const.
res = fmin_slsqp(self.fun, [-1.0, 1.0],
fprime=self.jac, args=(-1.0, ),
bounds = [(-0.8, 1.), (-1, 0.8)],
f_eqcons = self.f_eqcon,
fprime_eqcons = self.fprime_eqcon,
iprint = 0, full_output = 1)
x, fx, its, imode, smode = res
assert_(imode == 0, imode)
assert_array_almost_equal(x, [0.8, 0.8], decimal=3)
assert_(-0.8 <= x[0] <= 1)
assert_(-1 <= x[1] <= 0.8)
def test_scalar_constraints(self):
# Regression test for gh-2182
x = fmin_slsqp(lambda z: z**2, [3.],
ieqcons=[lambda z: z[0] - 1],
iprint=0)
assert_array_almost_equal(x, [1.])
x = fmin_slsqp(lambda z: z**2, [3.],
f_ieqcons=lambda z: [z[0] - 1],
iprint=0)
assert_array_almost_equal(x, [1.])
def test_integer_bounds(self):
# This should not raise an exception
fmin_slsqp(lambda z: z**2 - 1, [0], bounds=[[0, 1]], iprint=0)
def test_obj_must_return_scalar(self):
# Regression test for Github Issue #5433
# If objective function does not return a scalar, raises ValueError
with assert_raises(ValueError):
fmin_slsqp(lambda x: [0, 1], [1, 2, 3])
def test_obj_returns_scalar_in_list(self):
# Test for Github Issue #5433 and PR #6691
# Objective function should be able to return length-1 Python list
# containing the scalar
fmin_slsqp(lambda x: [0], [1, 2, 3], iprint=0)
def test_callback(self):
# Minimize, method='SLSQP': unbounded, approximated jacobian. Check for callback
callback = MyCallBack()
res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
method='SLSQP', callback=callback, options=self.opts)
assert_(res['success'], res['message'])
assert_(callback.been_called)
assert_equal(callback.ncalls, res['nit'])
def test_inconsistent_linearization(self):
# SLSQP must be able to solve this problem, even if the
# linearized problem at the starting point is infeasible.
# Linearized constraints are
#
# 2*x0[0]*x[0] >= 1
#
# At x0 = [0, 1], the second constraint is clearly infeasible.
# This triggers a call with n2==1 in the LSQ subroutine.
x = [0, 1]
f1 = lambda x: x[0] + x[1] - 2
f2 = lambda x: x[0]**2 - 1
sol = minimize(
lambda x: x[0]**2 + x[1]**2,
x,
constraints=({'type':'eq','fun': f1},
{'type':'ineq','fun': f2}),
bounds=((0,None), (0,None)),
method='SLSQP')
x = sol.x
assert_allclose(f1(x), 0, atol=1e-8)
assert_(f2(x) >= -1e-8)
assert_(sol.success, sol)
def test_regression_5743(self):
# SLSQP must not indicate success for this problem,
# which is infeasible.
x = [1, 2]
sol = minimize(
lambda x: x[0]**2 + x[1]**2,
x,
constraints=({'type':'eq','fun': lambda x: x[0]+x[1]-1},
{'type':'ineq','fun': lambda x: x[0]-2}),
bounds=((0,None), (0,None)),
method='SLSQP')
assert_(not sol.success, sol)
def test_gh_6676(self):
def func(x):
return (x[0] - 1)**2 + 2*(x[1] - 1)**2 + 0.5*(x[2] - 1)**2
sol = minimize(func, [0, 0, 0], method='SLSQP')
assert_(sol.jac.shape == (3,))
def test_invalid_bounds(self):
# Raise correct error when lower bound is greater than upper bound.
# See Github issue 6875.
bounds_list = [
((1, 2), (2, 1)),
((2, 1), (1, 2)),
((2, 1), (2, 1)),
((np.inf, 0), (np.inf, 0)),
((1, -np.inf), (0, 1)),
]
for bounds in bounds_list:
with assert_raises(ValueError):
minimize(self.fun, [-1.0, 1.0], bounds=bounds, method='SLSQP')
def test_bounds_clipping(self):
#
# SLSQP returns bogus results for initial guess out of bounds, gh-6859
#
def f(x):
return (x[0] - 1)**2
sol = minimize(f, [10], method='slsqp', bounds=[(None, 0)])
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
sol = minimize(f, [-10], method='slsqp', bounds=[(2, None)])
assert_(sol.success)
assert_allclose(sol.x, 2, atol=1e-10)
sol = minimize(f, [-10], method='slsqp', bounds=[(None, 0)])
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
sol = minimize(f, [10], method='slsqp', bounds=[(2, None)])
assert_(sol.success)
assert_allclose(sol.x, 2, atol=1e-10)
sol = minimize(f, [-0.5], method='slsqp', bounds=[(-1, 0)])
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
sol = minimize(f, [10], method='slsqp', bounds=[(-1, 0)])
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
def test_infeasible_initial(self):
# Check SLSQP behavior with infeasible initial point
def f(x):
x, = x
return x*x - 2*x + 1
cons_u = [{'type': 'ineq', 'fun': lambda x: 0 - x}]
cons_l = [{'type': 'ineq', 'fun': lambda x: x - 2}]
cons_ul = [{'type': 'ineq', 'fun': lambda x: 0 - x},
{'type': 'ineq', 'fun': lambda x: x + 1}]
sol = minimize(f, [10], method='slsqp', constraints=cons_u)
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
sol = minimize(f, [-10], method='slsqp', constraints=cons_l)
assert_(sol.success)
assert_allclose(sol.x, 2, atol=1e-10)
sol = minimize(f, [-10], method='slsqp', constraints=cons_u)
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
sol = minimize(f, [10], method='slsqp', constraints=cons_l)
assert_(sol.success)
assert_allclose(sol.x, 2, atol=1e-10)
sol = minimize(f, [-0.5], method='slsqp', constraints=cons_ul)
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
sol = minimize(f, [10], method='slsqp', constraints=cons_ul)
assert_(sol.success)
assert_allclose(sol.x, 0, atol=1e-10)
def test_inconsistent_inequalities(self):
# gh-7618
def cost(x):
return -1 * x[0] + 4 * x[1]
def ineqcons1(x):
return x[1] - x[0] - 1
def ineqcons2(x):
return x[0] - x[1]
# The inequalities are inconsistent, so no solution can exist:
#
# x1 >= x0 + 1
# x0 >= x1
x0 = (1,5)
bounds = ((-5, 5), (-5, 5))
cons = (dict(type='ineq', fun=ineqcons1), dict(type='ineq', fun=ineqcons2))
res = minimize(cost, x0, method='SLSQP', bounds=bounds, constraints=cons)
assert_(not res.success)
def test_new_bounds_type(self):
f = lambda x: x[0]**2 + x[1]**2
bounds = Bounds([1, 0], [np.inf, np.inf])
sol = minimize(f, [0, 0], method='slsqp', bounds=bounds)
assert_(sol.success)
assert_allclose(sol.x, [1, 0])
def test_nested_minimization(self):
class NestedProblem():
def __init__(self):
self.F_outer_count = 0
def F_outer(self, x):
self.F_outer_count += 1
if self.F_outer_count > 1000:
raise Exception("Nested minimization failed to terminate.")
inner_res = minimize(self.F_inner, (3, 4), method="SLSQP")
assert_(inner_res.success)
assert_allclose(inner_res.x, [1, 1])
return x[0]**2 + x[1]**2 + x[2]**2
def F_inner(self, x):
return (x[0] - 1)**2 + (x[1] - 1)**2
def solve(self):
outer_res = minimize(self.F_outer, (5, 5, 5), method="SLSQP")
assert_(outer_res.success)
assert_allclose(outer_res.x, [0, 0, 0])
problem = NestedProblem()
problem.solve()
@@ -0,0 +1,302 @@
"""
Unit tests for TNC optimization routine from tnc.py
"""
from numpy.testing import assert_allclose, assert_equal
from scipy import optimize
import numpy as np
from math import pow
class TestTnc(object):
"""TNC non-linear optimization.
These tests are taken from Prof. K. Schittkowski's test examples
for constrained non-linear programming.
http://www.uni-bayreuth.de/departments/math/~kschittkowski/home.htm
"""
def setup_method(self):
# options for minimize
self.opts = {'disp': False, 'maxiter': 200}
# objective functions and jacobian for each test
def f1(self, x, a=100.0):
return a * pow((x[1] - pow(x[0], 2)), 2) + pow(1.0 - x[0], 2)
def g1(self, x, a=100.0):
dif = [0, 0]
dif[1] = 2 * a * (x[1] - pow(x[0], 2))
dif[0] = -2.0 * (x[0] * (dif[1] - 1.0) + 1.0)
return dif
def fg1(self, x, a=100.0):
return self.f1(x, a), self.g1(x, a)
def f3(self, x):
return x[1] + pow(x[1] - x[0], 2) * 1.0e-5
def g3(self, x):
dif = [0, 0]
dif[0] = -2.0 * (x[1] - x[0]) * 1.0e-5
dif[1] = 1.0 - dif[0]
return dif
def fg3(self, x):
return self.f3(x), self.g3(x)
def f4(self, x):
return pow(x[0] + 1.0, 3) / 3.0 + x[1]
def g4(self, x):
dif = [0, 0]
dif[0] = pow(x[0] + 1.0, 2)
dif[1] = 1.0
return dif
def fg4(self, x):
return self.f4(x), self.g4(x)
def f5(self, x):
return np.sin(x[0] + x[1]) + pow(x[0] - x[1], 2) - \
1.5 * x[0] + 2.5 * x[1] + 1.0
def g5(self, x):
dif = [0, 0]
v1 = np.cos(x[0] + x[1])
v2 = 2.0*(x[0] - x[1])
dif[0] = v1 + v2 - 1.5
dif[1] = v1 - v2 + 2.5
return dif
def fg5(self, x):
return self.f5(x), self.g5(x)
def f38(self, x):
return (100.0 * pow(x[1] - pow(x[0], 2), 2) +
pow(1.0 - x[0], 2) + 90.0 * pow(x[3] - pow(x[2], 2), 2) +
pow(1.0 - x[2], 2) + 10.1 * (pow(x[1] - 1.0, 2) +
pow(x[3] - 1.0, 2)) +
19.8 * (x[1] - 1.0) * (x[3] - 1.0)) * 1.0e-5
def g38(self, x):
dif = [0, 0, 0, 0]
dif[0] = (-400.0 * x[0] * (x[1] - pow(x[0], 2)) -
2.0 * (1.0 - x[0])) * 1.0e-5
dif[1] = (200.0 * (x[1] - pow(x[0], 2)) + 20.2 * (x[1] - 1.0) +
19.8 * (x[3] - 1.0)) * 1.0e-5
dif[2] = (- 360.0 * x[2] * (x[3] - pow(x[2], 2)) -
2.0 * (1.0 - x[2])) * 1.0e-5
dif[3] = (180.0 * (x[3] - pow(x[2], 2)) + 20.2 * (x[3] - 1.0) +
19.8 * (x[1] - 1.0)) * 1.0e-5
return dif
def fg38(self, x):
return self.f38(x), self.g38(x)
def f45(self, x):
return 2.0 - x[0] * x[1] * x[2] * x[3] * x[4] / 120.0
def g45(self, x):
dif = [0] * 5
dif[0] = - x[1] * x[2] * x[3] * x[4] / 120.0
dif[1] = - x[0] * x[2] * x[3] * x[4] / 120.0
dif[2] = - x[0] * x[1] * x[3] * x[4] / 120.0
dif[3] = - x[0] * x[1] * x[2] * x[4] / 120.0
dif[4] = - x[0] * x[1] * x[2] * x[3] / 120.0
return dif
def fg45(self, x):
return self.f45(x), self.g45(x)
# tests
# minimize with method=TNC
def test_minimize_tnc1(self):
x0, bnds = [-2, 1], ([-np.inf, None], [-1.5, None])
xopt = [1, 1]
iterx = [] # to test callback
res = optimize.minimize(self.f1, x0, method='TNC', jac=self.g1,
bounds=bnds, options=self.opts,
callback=iterx.append)
assert_allclose(res.fun, self.f1(xopt), atol=1e-8)
assert_equal(len(iterx), res.nit)
def test_minimize_tnc1b(self):
x0, bnds = np.matrix([-2, 1]), ([-np.inf, None],[-1.5, None])
xopt = [1, 1]
x = optimize.minimize(self.f1, x0, method='TNC',
bounds=bnds, options=self.opts).x
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-4)
def test_minimize_tnc1c(self):
x0, bnds = [-2, 1], ([-np.inf, None],[-1.5, None])
xopt = [1, 1]
x = optimize.minimize(self.fg1, x0, method='TNC',
jac=True, bounds=bnds,
options=self.opts).x
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8)
def test_minimize_tnc2(self):
x0, bnds = [-2, 1], ([-np.inf, None], [1.5, None])
xopt = [-1.2210262419616387, 1.5]
x = optimize.minimize(self.f1, x0, method='TNC',
jac=self.g1, bounds=bnds,
options=self.opts).x
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8)
def test_minimize_tnc3(self):
x0, bnds = [10, 1], ([-np.inf, None], [0.0, None])
xopt = [0, 0]
x = optimize.minimize(self.f3, x0, method='TNC',
jac=self.g3, bounds=bnds,
options=self.opts).x
assert_allclose(self.f3(x), self.f3(xopt), atol=1e-8)
def test_minimize_tnc4(self):
x0,bnds = [1.125, 0.125], [(1, None), (0, None)]
xopt = [1, 0]
x = optimize.minimize(self.f4, x0, method='TNC',
jac=self.g4, bounds=bnds,
options=self.opts).x
assert_allclose(self.f4(x), self.f4(xopt), atol=1e-8)
def test_minimize_tnc5(self):
x0, bnds = [0, 0], [(-1.5, 4),(-3, 3)]
xopt = [-0.54719755119659763, -1.5471975511965976]
x = optimize.minimize(self.f5, x0, method='TNC',
jac=self.g5, bounds=bnds,
options=self.opts).x
assert_allclose(self.f5(x), self.f5(xopt), atol=1e-8)
def test_minimize_tnc38(self):
x0, bnds = np.array([-3, -1, -3, -1]), [(-10, 10)]*4
xopt = [1]*4
x = optimize.minimize(self.f38, x0, method='TNC',
jac=self.g38, bounds=bnds,
options=self.opts).x
assert_allclose(self.f38(x), self.f38(xopt), atol=1e-8)
def test_minimize_tnc45(self):
x0, bnds = [2] * 5, [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
xopt = [1, 2, 3, 4, 5]
x = optimize.minimize(self.f45, x0, method='TNC',
jac=self.g45, bounds=bnds,
options=self.opts).x
assert_allclose(self.f45(x), self.f45(xopt), atol=1e-8)
# fmin_tnc
def test_tnc1(self):
fg, x, bounds = self.fg1, [-2, 1], ([-np.inf, None], [-1.5, None])
xopt = [1, 1]
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds, args=(100.0, ),
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc1b(self):
x, bounds = [-2, 1], ([-np.inf, None], [-1.5, None])
xopt = [1, 1]
x, nf, rc = optimize.fmin_tnc(self.f1, x, approx_grad=True,
bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-4,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc1c(self):
x, bounds = [-2, 1], ([-np.inf, None], [-1.5, None])
xopt = [1, 1]
x, nf, rc = optimize.fmin_tnc(self.f1, x, fprime=self.g1,
bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc2(self):
fg, x, bounds = self.fg1, [-2, 1], ([-np.inf, None], [1.5, None])
xopt = [-1.2210262419616387, 1.5]
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc3(self):
fg, x, bounds = self.fg3, [10, 1], ([-np.inf, None], [0.0, None])
xopt = [0, 0]
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f3(x), self.f3(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc4(self):
fg, x, bounds = self.fg4, [1.125, 0.125], [(1, None), (0, None)]
xopt = [1, 0]
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f4(x), self.f4(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc5(self):
fg, x, bounds = self.fg5, [0, 0], [(-1.5, 4),(-3, 3)]
xopt = [-0.54719755119659763, -1.5471975511965976]
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f5(x), self.f5(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc38(self):
fg, x, bounds = self.fg38, np.array([-3, -1, -3, -1]), [(-10, 10)]*4
xopt = [1]*4
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f38(x), self.f38(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
def test_tnc45(self):
fg, x, bounds = self.fg45, [2] * 5, [(0, 1), (0, 2), (0, 3),
(0, 4), (0, 5)]
xopt = [1, 2, 3, 4, 5]
x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
messages=optimize.tnc.MSG_NONE,
maxfun=200)
assert_allclose(self.f45(x), self.f45(xopt), atol=1e-8,
err_msg="TNC failed with status: " +
optimize.tnc.RCSTRINGS[rc])
@@ -0,0 +1,106 @@
"""
Unit tests for trust-region optimization routines.
To run it in its simplest form::
nosetests test_optimize.py
"""
from __future__ import division, print_function, absolute_import
import numpy as np
from scipy.optimize import (minimize, rosen, rosen_der, rosen_hess,
rosen_hess_prod)
from numpy.testing import assert_, assert_equal, assert_allclose
class Accumulator:
""" This is for testing callbacks."""
def __init__(self):
self.count = 0
self.accum = None
def __call__(self, x):
self.count += 1
if self.accum is None:
self.accum = np.array(x)
else:
self.accum += x
class TestTrustRegionSolvers(object):
def setup_method(self):
self.x_opt = [1.0, 1.0]
self.easy_guess = [2.0, 2.0]
self.hard_guess = [-1.2, 1.0]
def test_dogleg_accuracy(self):
# test the accuracy and the return_all option
x0 = self.hard_guess
r = minimize(rosen, x0, jac=rosen_der, hess=rosen_hess, tol=1e-8,
method='dogleg', options={'return_all': True},)
assert_allclose(x0, r['allvecs'][0])
assert_allclose(r['x'], r['allvecs'][-1])
assert_allclose(r['x'], self.x_opt)
def test_dogleg_callback(self):
# test the callback mechanism and the maxiter and return_all options
accumulator = Accumulator()
maxiter = 5
r = minimize(rosen, self.hard_guess, jac=rosen_der, hess=rosen_hess,
callback=accumulator, method='dogleg',
options={'return_all': True, 'maxiter': maxiter},)
assert_equal(accumulator.count, maxiter)
assert_equal(len(r['allvecs']), maxiter+1)
assert_allclose(r['x'], r['allvecs'][-1])
assert_allclose(sum(r['allvecs'][1:]), accumulator.accum)
def test_solver_concordance(self):
# Assert that dogleg uses fewer iterations than ncg on the Rosenbrock
# test function, although this does not necessarily mean
# that dogleg is faster or better than ncg even for this function
# and especially not for other test functions.
f = rosen
g = rosen_der
h = rosen_hess
for x0 in (self.easy_guess, self.hard_guess):
r_dogleg = minimize(f, x0, jac=g, hess=h, tol=1e-8,
method='dogleg', options={'return_all': True})
r_trust_ncg = minimize(f, x0, jac=g, hess=h, tol=1e-8,
method='trust-ncg',
options={'return_all': True})
r_trust_krylov = minimize(f, x0, jac=g, hess=h, tol=1e-8,
method='trust-krylov',
options={'return_all': True})
r_ncg = minimize(f, x0, jac=g, hess=h, tol=1e-8,
method='newton-cg', options={'return_all': True})
r_iterative = minimize(f, x0, jac=g, hess=h, tol=1e-8,
method='trust-exact',
options={'return_all': True})
assert_allclose(self.x_opt, r_dogleg['x'])
assert_allclose(self.x_opt, r_trust_ncg['x'])
assert_allclose(self.x_opt, r_trust_krylov['x'])
assert_allclose(self.x_opt, r_ncg['x'])
assert_allclose(self.x_opt, r_iterative['x'])
assert_(len(r_dogleg['allvecs']) < len(r_ncg['allvecs']))
def test_trust_ncg_hessp(self):
for x0 in (self.easy_guess, self.hard_guess, self.x_opt):
r = minimize(rosen, x0, jac=rosen_der, hessp=rosen_hess_prod,
tol=1e-8, method='trust-ncg')
assert_allclose(self.x_opt, r['x'])
def test_trust_ncg_start_in_optimum(self):
r = minimize(rosen, x0=self.x_opt, jac=rosen_der, hess=rosen_hess,
tol=1e-8, method='trust-ncg')
assert_allclose(self.x_opt, r['x'])
def test_trust_krylov_start_in_optimum(self):
r = minimize(rosen, x0=self.x_opt, jac=rosen_der, hess=rosen_hess,
tol=1e-8, method='trust-krylov')
assert_allclose(self.x_opt, r['x'])
def test_trust_exact_start_in_optimum(self):
r = minimize(rosen, x0=self.x_opt, jac=rosen_der, hess=rosen_hess,
tol=1e-8, method='trust-exact')
assert_allclose(self.x_opt, r['x'])
@@ -0,0 +1,357 @@
"""
Unit tests for trust-region iterative subproblem.
To run it in its simplest form::
nosetests test_optimize.py
"""
from __future__ import division, print_function, absolute_import
import numpy as np
from scipy.optimize._trustregion_exact import (
estimate_smallest_singular_value,
singular_leading_submatrix,
IterativeSubproblem)
from scipy.linalg import (svd, get_lapack_funcs, det,
cho_factor, cho_solve, qr,
eigvalsh, eig, norm)
from numpy.testing import (assert_, assert_array_equal,
assert_equal, assert_array_almost_equal,
assert_array_less)
def random_entry(n, min_eig, max_eig, case):
# Generate random matrix
rand = np.random.uniform(-1, 1, (n, n))
# QR decomposition
Q, _, _ = qr(rand, pivoting='True')
# Generate random eigenvalues
eigvalues = np.random.uniform(min_eig, max_eig, n)
eigvalues = np.sort(eigvalues)[::-1]
# Generate matrix
Qaux = np.multiply(eigvalues, Q)
A = np.dot(Qaux, Q.T)
# Generate gradient vector accordingly
# to the case is being tested.
if case == 'hard':
g = np.zeros(n)
g[:-1] = np.random.uniform(-1, 1, n-1)
g = np.dot(Q, g)
elif case == 'jac_equal_zero':
g = np.zeros(n)
else:
g = np.random.uniform(-1, 1, n)
return A, g
class TestEstimateSmallestSingularValue(object):
def test_for_ill_condiotioned_matrix(self):
# Ill-conditioned triangular matrix
C = np.array([[1, 2, 3, 4],
[0, 0.05, 60, 7],
[0, 0, 0.8, 9],
[0, 0, 0, 10]])
# Get svd decomposition
U, s, Vt = svd(C)
# Get smallest singular value and correspondent right singular vector.
smin_svd = s[-1]
zmin_svd = Vt[-1, :]
# Estimate smallest singular value
smin, zmin = estimate_smallest_singular_value(C)
# Check the estimation
assert_array_almost_equal(smin, smin_svd, decimal=8)
assert_array_almost_equal(abs(zmin), abs(zmin_svd), decimal=8)
class TestSingularLeadingSubmatrix(object):
def test_for_already_singular_leading_submatrix(self):
# Define test matrix A.
# Note that the leading 2x2 submatrix is singular.
A = np.array([[1, 2, 3],
[2, 4, 5],
[3, 5, 6]])
# Get Cholesky from lapack functions
cholesky, = get_lapack_funcs(('potrf',), (A,))
# Compute Cholesky Decomposition
c, k = cholesky(A, lower=False, overwrite_a=False, clean=True)
delta, v = singular_leading_submatrix(A, c, k)
A[k-1, k-1] += delta
# Check if the leading submatrix is singular.
assert_array_almost_equal(det(A[:k, :k]), 0)
# Check if `v` fullfil the specified properties
quadratic_term = np.dot(v, np.dot(A, v))
assert_array_almost_equal(quadratic_term, 0)
def test_for_simetric_indefinite_matrix(self):
# Define test matrix A.
# Note that the leading 5x5 submatrix is indefinite.
A = np.asarray([[1, 2, 3, 7, 8],
[2, 5, 5, 9, 0],
[3, 5, 11, 1, 2],
[7, 9, 1, 7, 5],
[8, 0, 2, 5, 8]])
# Get Cholesky from lapack functions
cholesky, = get_lapack_funcs(('potrf',), (A,))
# Compute Cholesky Decomposition
c, k = cholesky(A, lower=False, overwrite_a=False, clean=True)
delta, v = singular_leading_submatrix(A, c, k)
A[k-1, k-1] += delta
# Check if the leading submatrix is singular.
assert_array_almost_equal(det(A[:k, :k]), 0)
# Check if `v` fullfil the specified properties
quadratic_term = np.dot(v, np.dot(A, v))
assert_array_almost_equal(quadratic_term, 0)
def test_for_first_element_equal_to_zero(self):
# Define test matrix A.
# Note that the leading 2x2 submatrix is singular.
A = np.array([[0, 3, 11],
[3, 12, 5],
[11, 5, 6]])
# Get Cholesky from lapack functions
cholesky, = get_lapack_funcs(('potrf',), (A,))
# Compute Cholesky Decomposition
c, k = cholesky(A, lower=False, overwrite_a=False, clean=True)
delta, v = singular_leading_submatrix(A, c, k)
A[k-1, k-1] += delta
# Check if the leading submatrix is singular
assert_array_almost_equal(det(A[:k, :k]), 0)
# Check if `v` fullfil the specified properties
quadratic_term = np.dot(v, np.dot(A, v))
assert_array_almost_equal(quadratic_term, 0)
class TestIterativeSubproblem(object):
def test_for_the_easy_case(self):
# `H` is chosen such that `g` is not orthogonal to the
# eigenvector associated with the smallest eigenvalue `s`.
H = [[10, 2, 3, 4],
[2, 1, 7, 1],
[3, 7, 1, 7],
[4, 1, 7, 2]]
g = [1, 1, 1, 1]
# Trust Radius
trust_radius = 1
# Solve Subproblem
subprob = IterativeSubproblem(x=0,
fun=lambda x: 0,
jac=lambda x: np.array(g),
hess=lambda x: np.array(H),
k_easy=1e-10,
k_hard=1e-10)
p, hits_boundary = subprob.solve(trust_radius)
assert_array_almost_equal(p, [0.00393332, -0.55260862,
0.67065477, -0.49480341])
assert_array_almost_equal(hits_boundary, True)
def test_for_the_hard_case(self):
# `H` is chosen such that `g` is orthogonal to the
# eigenvector associated with the smallest eigenvalue `s`.
H = [[10, 2, 3, 4],
[2, 1, 7, 1],
[3, 7, 1, 7],
[4, 1, 7, 2]]
g = [6.4852641521327437, 1, 1, 1]
s = -8.2151519874416614
# Trust Radius
trust_radius = 1
# Solve Subproblem
subprob = IterativeSubproblem(x=0,
fun=lambda x: 0,
jac=lambda x: np.array(g),
hess=lambda x: np.array(H),
k_easy=1e-10,
k_hard=1e-10)
p, hits_boundary = subprob.solve(trust_radius)
assert_array_almost_equal(-s, subprob.lambda_current)
def test_for_interior_convergence(self):
H = [[1.812159, 0.82687265, 0.21838879, -0.52487006, 0.25436988],
[0.82687265, 2.66380283, 0.31508988, -0.40144163, 0.08811588],
[0.21838879, 0.31508988, 2.38020726, -0.3166346, 0.27363867],
[-0.52487006, -0.40144163, -0.3166346, 1.61927182, -0.42140166],
[0.25436988, 0.08811588, 0.27363867, -0.42140166, 1.33243101]]
g = [0.75798952, 0.01421945, 0.33847612, 0.83725004, -0.47909534]
# Solve Subproblem
subprob = IterativeSubproblem(x=0,
fun=lambda x: 0,
jac=lambda x: np.array(g),
hess=lambda x: np.array(H))
p, hits_boundary = subprob.solve(1.1)
assert_array_almost_equal(p, [-0.68585435, 0.1222621, -0.22090999,
-0.67005053, 0.31586769])
assert_array_almost_equal(hits_boundary, False)
assert_array_almost_equal(subprob.lambda_current, 0)
assert_array_almost_equal(subprob.niter, 1)
def test_for_jac_equal_zero(self):
H = [[0.88547534, 2.90692271, 0.98440885, -0.78911503, -0.28035809],
[2.90692271, -0.04618819, 0.32867263, -0.83737945, 0.17116396],
[0.98440885, 0.32867263, -0.87355957, -0.06521957, -1.43030957],
[-0.78911503, -0.83737945, -0.06521957, -1.645709, -0.33887298],
[-0.28035809, 0.17116396, -1.43030957, -0.33887298, -1.68586978]]
g = [0, 0, 0, 0, 0]
# Solve Subproblem
subprob = IterativeSubproblem(x=0,
fun=lambda x: 0,
jac=lambda x: np.array(g),
hess=lambda x: np.array(H),
k_easy=1e-10,
k_hard=1e-10)
p, hits_boundary = subprob.solve(1.1)
assert_array_almost_equal(p, [0.06910534, -0.01432721,
-0.65311947, -0.23815972,
-0.84954934])
assert_array_almost_equal(hits_boundary, True)
def test_for_jac_very_close_to_zero(self):
H = [[0.88547534, 2.90692271, 0.98440885, -0.78911503, -0.28035809],
[2.90692271, -0.04618819, 0.32867263, -0.83737945, 0.17116396],
[0.98440885, 0.32867263, -0.87355957, -0.06521957, -1.43030957],
[-0.78911503, -0.83737945, -0.06521957, -1.645709, -0.33887298],
[-0.28035809, 0.17116396, -1.43030957, -0.33887298, -1.68586978]]
g = [0, 0, 0, 0, 1e-15]
# Solve Subproblem
subprob = IterativeSubproblem(x=0,
fun=lambda x: 0,
jac=lambda x: np.array(g),
hess=lambda x: np.array(H),
k_easy=1e-10,
k_hard=1e-10)
p, hits_boundary = subprob.solve(1.1)
assert_array_almost_equal(p, [0.06910534, -0.01432721,
-0.65311947, -0.23815972,
-0.84954934])
assert_array_almost_equal(hits_boundary, True)
def test_for_random_entries(self):
# Seed
np.random.seed(1)
# Dimension
n = 5
for case in ('easy', 'hard', 'jac_equal_zero'):
eig_limits = [(-20, -15),
(-10, -5),
(-10, 0),
(-5, 5),
(-10, 10),
(0, 10),
(5, 10),
(15, 20)]
for min_eig, max_eig in eig_limits:
# Generate random symmetric matrix H with
# eigenvalues between min_eig and max_eig.
H, g = random_entry(n, min_eig, max_eig, case)
# Trust radius
trust_radius_list = [0.1, 0.3, 0.6, 0.8, 1, 1.2, 3.3, 5.5, 10]
for trust_radius in trust_radius_list:
# Solve subproblem with very high accuracy
subprob_ac = IterativeSubproblem(0,
lambda x: 0,
lambda x: g,
lambda x: H,
k_easy=1e-10,
k_hard=1e-10)
p_ac, hits_boundary_ac = subprob_ac.solve(trust_radius)
# Compute objective function value
J_ac = 1/2*np.dot(p_ac, np.dot(H, p_ac))+np.dot(g, p_ac)
stop_criteria = [(0.1, 2),
(0.5, 1.1),
(0.9, 1.01)]
for k_opt, k_trf in stop_criteria:
# k_easy and k_hard computed in function
# of k_opt and k_trf accordingly to
# Conn, A. R., Gould, N. I., & Toint, P. L. (2000).
# "Trust region methods". Siam. p. 197.
k_easy = min(k_trf-1,
1-np.sqrt(k_opt))
k_hard = 1-k_opt
# Solve subproblem
subprob = IterativeSubproblem(0,
lambda x: 0,
lambda x: g,
lambda x: H,
k_easy=k_easy,
k_hard=k_hard)
p, hits_boundary = subprob.solve(trust_radius)
# Compute objective function value
J = 1/2*np.dot(p, np.dot(H, p))+np.dot(g, p)
# Check if it respect k_trf
if hits_boundary:
assert_array_equal(np.abs(norm(p)-trust_radius) <=
(k_trf-1)*trust_radius, True)
else:
assert_equal(norm(p) <= trust_radius, True)
# Check if it respect k_opt
assert_equal(J <= k_opt*J_ac, True)
@@ -0,0 +1,173 @@
"""
Unit tests for Krylov space trust-region subproblem solver.
To run it in its simplest form::
nosetests test_optimize.py
"""
from __future__ import division, print_function, absolute_import
import numpy as np
from scipy.optimize._trlib import (get_trlib_quadratic_subproblem)
from numpy.testing import (assert_, assert_array_equal,
assert_almost_equal,
assert_equal, assert_array_almost_equal,
assert_array_less)
KrylovQP = get_trlib_quadratic_subproblem(tol_rel_i=1e-8, tol_rel_b=1e-6)
KrylovQP_disp = get_trlib_quadratic_subproblem(tol_rel_i=1e-8, tol_rel_b=1e-6, disp=True)
class TestKrylovQuadraticSubproblem(object):
def test_for_the_easy_case(self):
# `H` is chosen such that `g` is not orthogonal to the
# eigenvector associated with the smallest eigenvalue.
H = np.array([[1.0, 0.0, 4.0],
[0.0, 2.0, 0.0],
[4.0, 0.0, 3.0]])
g = np.array([5.0, 0.0, 4.0])
# Trust Radius
trust_radius = 1.0
# Solve Subproblem
subprob = KrylovQP(x=0,
fun=lambda x: 0,
jac=lambda x: g,
hess=lambda x: None,
hessp=lambda x, y: H.dot(y))
p, hits_boundary = subprob.solve(trust_radius)
assert_array_almost_equal(p, np.array([-1.0, 0.0, 0.0]))
assert_equal(hits_boundary, True)
# check kkt satisfaction
assert_almost_equal(
np.linalg.norm(H.dot(p) + subprob.lam * p + g),
0.0)
# check trust region constraint
assert_almost_equal(np.linalg.norm(p), trust_radius)
trust_radius = 0.5
p, hits_boundary = subprob.solve(trust_radius)
assert_array_almost_equal(p,
np.array([-0.46125446, 0., -0.19298788]))
assert_equal(hits_boundary, True)
# check kkt satisfaction
assert_almost_equal(
np.linalg.norm(H.dot(p) + subprob.lam * p + g),
0.0)
# check trust region constraint
assert_almost_equal(np.linalg.norm(p), trust_radius)
def test_for_the_hard_case(self):
# `H` is chosen such that `g` is orthogonal to the
# eigenvector associated with the smallest eigenvalue.
H = np.array([[1.0, 0.0, 4.0],
[0.0, 2.0, 0.0],
[4.0, 0.0, 3.0]])
g = np.array([0.0, 2.0, 0.0])
# Trust Radius
trust_radius = 1.0
# Solve Subproblem
subprob = KrylovQP(x=0,
fun=lambda x: 0,
jac=lambda x: g,
hess=lambda x: None,
hessp=lambda x, y: H.dot(y))
p, hits_boundary = subprob.solve(trust_radius)
assert_array_almost_equal(p, np.array([0.0, -1.0, 0.0]))
# check kkt satisfaction
assert_almost_equal(
np.linalg.norm(H.dot(p) + subprob.lam * p + g),
0.0)
# check trust region constraint
assert_almost_equal(np.linalg.norm(p), trust_radius)
trust_radius = 0.5
p, hits_boundary = subprob.solve(trust_radius)
assert_array_almost_equal(p, np.array([0.0, -0.5, 0.0]))
# check kkt satisfaction
assert_almost_equal(
np.linalg.norm(H.dot(p) + subprob.lam * p + g),
0.0)
# check trust region constraint
assert_almost_equal(np.linalg.norm(p), trust_radius)
def test_for_interior_convergence(self):
H = np.array([[1.812159, 0.82687265, 0.21838879, -0.52487006, 0.25436988],
[0.82687265, 2.66380283, 0.31508988, -0.40144163, 0.08811588],
[0.21838879, 0.31508988, 2.38020726, -0.3166346, 0.27363867],
[-0.52487006, -0.40144163, -0.3166346, 1.61927182, -0.42140166],
[0.25436988, 0.08811588, 0.27363867, -0.42140166, 1.33243101]])
g = np.array([0.75798952, 0.01421945, 0.33847612, 0.83725004, -0.47909534])
trust_radius = 1.1
# Solve Subproblem
subprob = KrylovQP(x=0,
fun=lambda x: 0,
jac=lambda x: g,
hess=lambda x: None,
hessp=lambda x, y: H.dot(y))
p, hits_boundary = subprob.solve(trust_radius)
# check kkt satisfaction
assert_almost_equal(
np.linalg.norm(H.dot(p) + subprob.lam * p + g),
0.0)
assert_array_almost_equal(p, [-0.68585435, 0.1222621, -0.22090999,
-0.67005053, 0.31586769])
assert_array_almost_equal(hits_boundary, False)
def test_for_very_close_to_zero(self):
H = np.array([[0.88547534, 2.90692271, 0.98440885, -0.78911503, -0.28035809],
[2.90692271, -0.04618819, 0.32867263, -0.83737945, 0.17116396],
[0.98440885, 0.32867263, -0.87355957, -0.06521957, -1.43030957],
[-0.78911503, -0.83737945, -0.06521957, -1.645709, -0.33887298],
[-0.28035809, 0.17116396, -1.43030957, -0.33887298, -1.68586978]])
g = np.array([0, 0, 0, 0, 1e-6])
trust_radius = 1.1
# Solve Subproblem
subprob = KrylovQP(x=0,
fun=lambda x: 0,
jac=lambda x: g,
hess=lambda x: None,
hessp=lambda x, y: H.dot(y))
p, hits_boundary = subprob.solve(trust_radius)
# check kkt satisfaction
assert_almost_equal(
np.linalg.norm(H.dot(p) + subprob.lam * p + g),
0.0)
# check trust region constraint
assert_almost_equal(np.linalg.norm(p), trust_radius)
assert_array_almost_equal(p, [0.06910534, -0.01432721,
-0.65311947, -0.23815972,
-0.84954934])
assert_array_almost_equal(hits_boundary, True)
def test_disp(self, capsys):
H = -np.eye(5)
g = np.array([0, 0, 0, 0, 1e-6])
trust_radius = 1.1
subprob = KrylovQP_disp(x=0,
fun=lambda x: 0,
jac=lambda x: g,
hess=lambda x: None,
hessp=lambda x, y: H.dot(y))
p, hits_boundary = subprob.solve(trust_radius)
out, err = capsys.readouterr()
assert_(out.startswith(' TR Solving trust region problem'), repr(out))
@@ -0,0 +1,621 @@
from __future__ import division, print_function, absolute_import
import pytest
from math import sqrt, exp, sin, cos
from numpy.testing import (assert_warns, assert_,
assert_allclose,
assert_equal)
import numpy as np
from numpy import finfo, power, nan, isclose
from scipy.optimize import zeros, newton, root_scalar
from scipy._lib._util import getargspec_no_self as _getargspec
# Import testing parameters
from scipy.optimize._tstutils import get_tests, functions as tstutils_functions, fstrings as tstutils_fstrings
from scipy._lib._numpy_compat import suppress_warnings
TOL = 4*np.finfo(float).eps # tolerance
_FLOAT_EPS = finfo(float).eps
# A few test functions used frequently:
# # A simple quadratic, (x-1)^2 - 1
def f1(x):
return x ** 2 - 2 * x - 1
def f1_1(x):
return 2 * x - 2
def f1_2(x):
return 2.0 + 0 * x
def f1_and_p_and_pp(x):
return f1(x), f1_1(x), f1_2(x)
# Simple transcendental function
def f2(x):
return exp(x) - cos(x)
def f2_1(x):
return exp(x) + sin(x)
def f2_2(x):
return exp(x) + cos(x)
class TestBasic(object):
def run_check_by_name(self, name, smoothness=0, **kwargs):
a = .5
b = sqrt(3)
xtol = 4*np.finfo(float).eps
rtol = 4*np.finfo(float).eps
for function, fname in zip(tstutils_functions, tstutils_fstrings):
if smoothness > 0 and fname in ['f4', 'f5', 'f6']:
continue
r = root_scalar(function, method=name, bracket=[a, b], x0=a,
xtol=xtol, rtol=rtol, **kwargs)
zero = r.root
assert_(r.converged)
assert_allclose(zero, 1.0, atol=xtol, rtol=rtol,
err_msg='method %s, function %s' % (name, fname))
def run_check(self, method, name):
a = .5
b = sqrt(3)
xtol = 4 * _FLOAT_EPS
rtol = 4 * _FLOAT_EPS
for function, fname in zip(tstutils_functions, tstutils_fstrings):
zero, r = method(function, a, b, xtol=xtol, rtol=rtol,
full_output=True)
assert_(r.converged)
assert_allclose(zero, 1.0, atol=xtol, rtol=rtol,
err_msg='method %s, function %s' % (name, fname))
def _run_one_test(self, tc, method, sig_args_keys=None,
sig_kwargs_keys=None, **kwargs):
method_args = []
for k in sig_args_keys or []:
if k not in tc:
# If a,b not present use x0, x1. Similarly for f and func
k = {'a': 'x0', 'b': 'x1', 'func': 'f'}.get(k, k)
method_args.append(tc[k])
method_kwargs = dict(**kwargs)
method_kwargs.update({'full_output': True, 'disp': False})
for k in sig_kwargs_keys or []:
method_kwargs[k] = tc[k]
root = tc.get('root')
func_args = tc.get('args', ())
try:
r, rr = method(*method_args, args=func_args, **method_kwargs)
return root, rr, tc
except Exception:
return root, zeros.RootResults(nan, -1, -1, zeros._EVALUEERR), tc
def run_tests(self, tests, method, name,
xtol=4 * _FLOAT_EPS, rtol=4 * _FLOAT_EPS,
known_fail=None, **kwargs):
r"""Run test-cases using the specified method and the supplied signature.
Extract the arguments for the method call from the test case
dictionary using the supplied keys for the method's signature."""
# The methods have one of two base signatures:
# (f, a, b, **kwargs) # newton
# (func, x0, **kwargs) # bisect/brentq/...
sig = _getargspec(method) # ArgSpec with args, varargs, varkw, defaults
nDefaults = len(sig[3])
nRequired = len(sig[0]) - nDefaults
sig_args_keys = sig[0][:nRequired]
sig_kwargs_keys = []
if name in ['secant', 'newton', 'halley']:
if name in ['newton', 'halley']:
sig_kwargs_keys.append('fprime')
if name in ['halley']:
sig_kwargs_keys.append('fprime2')
kwargs['tol'] = xtol
else:
kwargs['xtol'] = xtol
kwargs['rtol'] = rtol
results = [list(self._run_one_test(
tc, method, sig_args_keys=sig_args_keys,
sig_kwargs_keys=sig_kwargs_keys, **kwargs)) for tc in tests]
# results= [[true root, full output, tc], ...]
known_fail = known_fail or []
notcvgd = [elt for elt in results if not elt[1].converged]
notcvgd = [elt for elt in notcvgd if elt[-1]['ID'] not in known_fail]
notcvged_IDS = [elt[-1]['ID'] for elt in notcvgd]
assert_equal([len(notcvged_IDS), notcvged_IDS], [0, []])
# The usable xtol and rtol depend on the test
tols = {'xtol': 4 * _FLOAT_EPS, 'rtol': 4 * _FLOAT_EPS}
tols.update(**kwargs)
rtol = tols['rtol']
atol = tols.get('tol', tols['xtol'])
cvgd = [elt for elt in results if elt[1].converged]
approx = [elt[1].root for elt in cvgd]
correct = [elt[0] for elt in cvgd]
notclose = [[a] + elt for a, c, elt in zip(approx, correct, cvgd) if
not isclose(a, c, rtol=rtol, atol=atol)
and elt[-1]['ID'] not in known_fail]
# Evaluate the function and see if is 0 at the purported root
fvs = [tc['f'](aroot, *(tc['args'])) for aroot, c, fullout, tc in notclose]
notclose = [[fv] + elt for fv, elt in zip(fvs, notclose) if fv != 0]
assert_equal([notclose, len(notclose)], [[], 0])
def run_collection(self, collection, method, name, smoothness=None,
known_fail=None,
xtol=4 * _FLOAT_EPS, rtol=4 * _FLOAT_EPS,
**kwargs):
r"""Run a collection of tests using the specified method.
The name is used to determine some optional arguments."""
tests = get_tests(collection, smoothness=smoothness)
self.run_tests(tests, method, name, xtol=xtol, rtol=rtol,
known_fail=known_fail, **kwargs)
def test_bisect(self):
self.run_check(zeros.bisect, 'bisect')
self.run_check_by_name('bisect')
self.run_collection('aps', zeros.bisect, 'bisect', smoothness=1)
def test_ridder(self):
self.run_check(zeros.ridder, 'ridder')
self.run_check_by_name('ridder')
self.run_collection('aps', zeros.ridder, 'ridder', smoothness=1)
def test_brentq(self):
self.run_check(zeros.brentq, 'brentq')
self.run_check_by_name('brentq')
# Brentq/h needs a lower tolerance to be specified
self.run_collection('aps', zeros.brentq, 'brentq', smoothness=1,
xtol=1e-14, rtol=1e-14)
def test_brenth(self):
self.run_check(zeros.brenth, 'brenth')
self.run_check_by_name('brenth')
self.run_collection('aps', zeros.brenth, 'brenth', smoothness=1,
xtol=1e-14, rtol=1e-14)
def test_toms748(self):
self.run_check(zeros.toms748, 'toms748')
self.run_check_by_name('toms748')
self.run_collection('aps', zeros.toms748, 'toms748', smoothness=1)
def test_newton_collections(self):
known_fail = ['aps.13.00']
known_fail += ['aps.12.05', 'aps.12.17'] # fails under Windows Py27
for collection in ['aps', 'complex']:
self.run_collection(collection, zeros.newton, 'newton',
smoothness=2, known_fail=known_fail)
def test_halley_collections(self):
known_fail = ['aps.12.06', 'aps.12.07', 'aps.12.08', 'aps.12.09',
'aps.12.10', 'aps.12.11', 'aps.12.12', 'aps.12.13',
'aps.12.14', 'aps.12.15', 'aps.12.16', 'aps.12.17',
'aps.12.18', 'aps.13.00']
for collection in ['aps', 'complex']:
self.run_collection(collection, zeros.newton, 'halley',
smoothness=2, known_fail=known_fail)
@staticmethod
def f1(x):
return x**2 - 2*x - 1 # == (x-1)**2 - 2
@staticmethod
def f1_1(x):
return 2*x - 2
@staticmethod
def f1_2(x):
return 2.0 + 0*x
@staticmethod
def f2(x):
return exp(x) - cos(x)
@staticmethod
def f2_1(x):
return exp(x) + sin(x)
@staticmethod
def f2_2(x):
return exp(x) + cos(x)
def test_newton(self):
for f, f_1, f_2 in [(self.f1, self.f1_1, self.f1_2),
(self.f2, self.f2_1, self.f2_2)]:
x = zeros.newton(f, 3, tol=1e-6)
assert_allclose(f(x), 0, atol=1e-6)
x = zeros.newton(f, 3, x1=5, tol=1e-6) # secant, x0 and x1
assert_allclose(f(x), 0, atol=1e-6)
x = zeros.newton(f, 3, fprime=f_1, tol=1e-6) # newton
assert_allclose(f(x), 0, atol=1e-6)
x = zeros.newton(f, 3, fprime=f_1, fprime2=f_2, tol=1e-6) # halley
assert_allclose(f(x), 0, atol=1e-6)
def test_newton_by_name(self):
r"""Invoke newton through root_scalar()"""
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]:
r = root_scalar(f, method='newton', x0=3, fprime=f_1, xtol=1e-6)
assert_allclose(f(r.root), 0, atol=1e-6)
def test_secant_by_name(self):
r"""Invoke secant through root_scalar()"""
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]:
r = root_scalar(f, method='secant', x0=3, x1=2, xtol=1e-6)
assert_allclose(f(r.root), 0, atol=1e-6)
r = root_scalar(f, method='secant', x0=3, x1=5, xtol=1e-6)
assert_allclose(f(r.root), 0, atol=1e-6)
def test_halley_by_name(self):
r"""Invoke halley through root_scalar()"""
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]:
r = root_scalar(f, method='halley', x0=3,
fprime=f_1, fprime2=f_2, xtol=1e-6)
assert_allclose(f(r.root), 0, atol=1e-6)
def test_root_scalar_fail(self):
with pytest.raises(ValueError):
root_scalar(f1, method='secant', x0=3, xtol=1e-6) # no x1
with pytest.raises(ValueError):
root_scalar(f1, method='newton', x0=3, xtol=1e-6) # no fprime
with pytest.raises(ValueError):
root_scalar(f1, method='halley', fprime=f1_1, x0=3, xtol=1e-6) # no fprime2
with pytest.raises(ValueError):
root_scalar(f1, method='halley', fprime2=f1_2, x0=3, xtol=1e-6) # no fprime
def test_array_newton(self):
"""test newton with array"""
def f1(x, *a):
b = a[0] + x * a[3]
return a[1] - a[2] * (np.exp(b / a[5]) - 1.0) - b / a[4] - x
def f1_1(x, *a):
b = a[3] / a[5]
return -a[2] * np.exp(a[0] / a[5] + x * b) * b - a[3] / a[4] - 1
def f1_2(x, *a):
b = a[3] / a[5]
return -a[2] * np.exp(a[0] / a[5] + x * b) * b**2
a0 = np.array([
5.32725221, 5.48673747, 5.49539973,
5.36387202, 4.80237316, 1.43764452,
5.23063958, 5.46094772, 5.50512718,
5.42046290
])
a1 = (np.sin(range(10)) + 1.0) * 7.0
args = (a0, a1, 1e-09, 0.004, 10, 0.27456)
x0 = [7.0] * 10
x = zeros.newton(f1, x0, f1_1, args)
x_expected = (
6.17264965, 11.7702805, 12.2219954,
7.11017681, 1.18151293, 0.143707955,
4.31928228, 10.5419107, 12.7552490,
8.91225749
)
assert_allclose(x, x_expected)
# test halley's
x = zeros.newton(f1, x0, f1_1, args, fprime2=f1_2)
assert_allclose(x, x_expected)
# test secant
x = zeros.newton(f1, x0, args=args)
assert_allclose(x, x_expected)
def test_array_secant_active_zero_der(self):
"""test secant doesn't continue to iterate zero derivatives"""
x = zeros.newton(lambda x, *a: x*x - a[0], x0=[4.123, 5],
args=[np.array([17, 25])])
assert_allclose(x, (4.123105625617661, 5.0))
def test_array_newton_integers(self):
# test secant with float
x = zeros.newton(lambda y, z: z - y ** 2, [4.0] * 2,
args=([15.0, 17.0],))
assert_allclose(x, (3.872983346207417, 4.123105625617661))
# test integer becomes float
x = zeros.newton(lambda y, z: z - y ** 2, [4] * 2, args=([15, 17],))
assert_allclose(x, (3.872983346207417, 4.123105625617661))
def test_array_newton_zero_der_failures(self):
# test derivative zero warning
assert_warns(RuntimeWarning, zeros.newton,
lambda y: y**2 - 2, [0., 0.], lambda y: 2 * y)
# test failures and zero_der
with pytest.warns(RuntimeWarning):
results = zeros.newton(lambda y: y**2 - 2, [0., 0.],
lambda y: 2*y, full_output=True)
assert_allclose(results.root, 0)
assert results.zero_der.all()
assert not results.converged.any()
def test_newton_combined(self):
f1 = lambda x: x**2 - 2*x - 1
f1_1 = lambda x: 2*x - 2
f1_2 = lambda x: 2.0 + 0*x
def f1_and_p_and_pp(x):
return x**2 - 2*x-1, 2*x-2, 2.0
sol0 = root_scalar(f1, method='newton', x0=3, fprime=f1_1)
sol = root_scalar(f1_and_p_and_pp, method='newton', x0=3, fprime=True)
assert_allclose(sol0.root, sol.root, atol=1e-8)
assert_equal(2*sol.function_calls, sol0.function_calls)
sol0 = root_scalar(f1, method='halley', x0=3, fprime=f1_1, fprime2=f1_2)
sol = root_scalar(f1_and_p_and_pp, method='halley', x0=3, fprime2=True)
assert_allclose(sol0.root, sol.root, atol=1e-8)
assert_equal(3*sol.function_calls, sol0.function_calls)
def test_newton_full_output(self):
# Test the full_output capability, both when converging and not.
# Use simple polynomials, to avoid hitting platform dependencies
# (e.g. exp & trig) in number of iterations
x0 = 3
expected_counts = [(6, 7), (5, 10), (3, 9)]
for derivs in range(3):
kwargs = {'tol': 1e-6, 'full_output': True, }
for k, v in [['fprime', self.f1_1], ['fprime2', self.f1_2]][:derivs]:
kwargs[k] = v
x, r = zeros.newton(self.f1, x0, disp=False, **kwargs)
assert_(r.converged)
assert_equal(x, r.root)
assert_equal((r.iterations, r.function_calls), expected_counts[derivs])
if derivs == 0:
assert(r.function_calls <= r.iterations + 1)
else:
assert_equal(r.function_calls, (derivs + 1) * r.iterations)
# Now repeat, allowing one fewer iteration to force convergence failure
iters = r.iterations - 1
x, r = zeros.newton(self.f1, x0, maxiter=iters, disp=False, **kwargs)
assert_(not r.converged)
assert_equal(x, r.root)
assert_equal(r.iterations, iters)
if derivs == 1:
# Check that the correct Exception is raised and
# validate the start of the message.
with pytest.raises(
RuntimeError,
match='Failed to converge after %d iterations, value is .*' % (iters)):
x, r = zeros.newton(self.f1, x0, maxiter=iters, disp=True, **kwargs)
def test_deriv_zero_warning(self):
func = lambda x: x**2 - 2.0
dfunc = lambda x: 2*x
assert_warns(RuntimeWarning, zeros.newton, func, 0.0, dfunc)
def test_gh_5555():
root = 0.1
def f(x):
return x - root
methods = [zeros.bisect, zeros.ridder]
xtol = rtol = TOL
for method in methods:
res = method(f, -1e8, 1e7, xtol=xtol, rtol=rtol)
assert_allclose(root, res, atol=xtol, rtol=rtol,
err_msg='method %s' % method.__name__)
def test_gh_5557():
# Show that without the changes in 5557 brentq and brenth might
# only achieve a tolerance of 2*(xtol + rtol*|res|).
# f linearly interpolates (0, -0.1), (0.5, -0.1), and (1,
# 0.4). The important parts are that |f(0)| < |f(1)| (so that
# brent takes 0 as the initial guess), |f(0)| < atol (so that
# brent accepts 0 as the root), and that the exact root of f lies
# more than atol away from 0 (so that brent doesn't achieve the
# desired tolerance).
def f(x):
if x < 0.5:
return -0.1
else:
return x - 0.6
atol = 0.51
rtol = 4 * _FLOAT_EPS
methods = [zeros.brentq, zeros.brenth]
for method in methods:
res = method(f, 0, 1, xtol=atol, rtol=rtol)
assert_allclose(0.6, res, atol=atol, rtol=rtol)
class TestRootResults:
def test_repr(self):
r = zeros.RootResults(root=1.0,
iterations=44,
function_calls=46,
flag=0)
expected_repr = (" converged: True\n flag: 'converged'"
"\n function_calls: 46\n iterations: 44\n"
" root: 1.0")
assert_equal(repr(r), expected_repr)
def test_complex_halley():
"""Test Halley's works with complex roots"""
def f(x, *a):
return a[0] * x**2 + a[1] * x + a[2]
def f_1(x, *a):
return 2 * a[0] * x + a[1]
def f_2(x, *a):
retval = 2 * a[0]
try:
size = len(x)
except TypeError:
return retval
else:
return [retval] * size
z = complex(1.0, 2.0)
coeffs = (2.0, 3.0, 4.0)
y = zeros.newton(f, z, args=coeffs, fprime=f_1, fprime2=f_2, tol=1e-6)
# (-0.75000000000000078+1.1989578808281789j)
assert_allclose(f(y, *coeffs), 0, atol=1e-6)
z = [z] * 10
coeffs = (2.0, 3.0, 4.0)
y = zeros.newton(f, z, args=coeffs, fprime=f_1, fprime2=f_2, tol=1e-6)
assert_allclose(f(y, *coeffs), 0, atol=1e-6)
def test_zero_der_nz_dp():
"""Test secant method with a non-zero dp, but an infinite newton step"""
# pick a symmetrical functions and choose a point on the side that with dx
# makes a secant that is a flat line with zero slope, EG: f = (x - 100)**2,
# which has a root at x = 100 and is symmetrical around the line x = 100
# we have to pick a really big number so that it is consistently true
# now find a point on each side so that the secant has a zero slope
dx = np.finfo(float).eps ** 0.33
# 100 - p0 = p1 - 100 = p0 * (1 + dx) + dx - 100
# -> 200 = p0 * (2 + dx) + dx
p0 = (200.0 - dx) / (2.0 + dx)
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "RMS of")
x = zeros.newton(lambda y: (y - 100.0)**2, x0=[p0] * 10)
assert_allclose(x, [100] * 10)
# test scalar cases too
p0 = (2.0 - 1e-4) / (2.0 + 1e-4)
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "Tolerance of")
x = zeros.newton(lambda y: (y - 1.0) ** 2, x0=p0)
assert_allclose(x, 1)
p0 = (-2.0 + 1e-4) / (2.0 + 1e-4)
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "Tolerance of")
x = zeros.newton(lambda y: (y + 1.0) ** 2, x0=p0)
assert_allclose(x, -1)
def test_array_newton_failures():
"""Test that array newton fails as expected"""
# p = 0.68 # [MPa]
# dp = -0.068 * 1e6 # [Pa]
# T = 323 # [K]
diameter = 0.10 # [m]
# L = 100 # [m]
roughness = 0.00015 # [m]
rho = 988.1 # [kg/m**3]
mu = 5.4790e-04 # [Pa*s]
u = 2.488 # [m/s]
reynolds_number = rho * u * diameter / mu # Reynolds number
def colebrook_eqn(darcy_friction, re, dia):
return (1 / np.sqrt(darcy_friction) +
2 * np.log10(roughness / 3.7 / dia +
2.51 / re / np.sqrt(darcy_friction)))
# only some failures
with pytest.warns(RuntimeWarning):
result = zeros.newton(
colebrook_eqn, x0=[0.01, 0.2, 0.02223, 0.3], maxiter=2,
args=[reynolds_number, diameter], full_output=True
)
assert not result.converged.all()
# they all fail
with pytest.raises(RuntimeError):
result = zeros.newton(
colebrook_eqn, x0=[0.01] * 2, maxiter=2,
args=[reynolds_number, diameter], full_output=True
)
# this test should **not** raise a RuntimeWarning
def test_gh8904_zeroder_at_root_fails():
"""Test that Newton or Halley don't warn if zero derivative at root"""
# a function that has a zero derivative at it's root
def f_zeroder_root(x):
return x**3 - x**2
# should work with secant
r = zeros.newton(f_zeroder_root, x0=0)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# test again with array
r = zeros.newton(f_zeroder_root, x0=[0]*10)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# 1st derivative
def fder(x):
return 3 * x**2 - 2 * x
# 2nd derivative
def fder2(x):
return 6*x - 2
# should work with newton and halley
r = zeros.newton(f_zeroder_root, x0=0, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
r = zeros.newton(f_zeroder_root, x0=0, fprime=fder,
fprime2=fder2)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# test again with array
r = zeros.newton(f_zeroder_root, x0=[0]*10, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
r = zeros.newton(f_zeroder_root, x0=[0]*10, fprime=fder,
fprime2=fder2)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# also test that if a root is found we do not raise RuntimeWarning even if
# the derivative is zero, EG: at x = 0.5, then fval = -0.125 and
# fder = -0.25 so the next guess is 0.5 - (-0.125/-0.5) = 0 which is the
# root, but if the solver continued with that guess, then it will calculate
# a zero derivative, so it should return the root w/o RuntimeWarning
r = zeros.newton(f_zeroder_root, x0=0.5, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# test again with array
r = zeros.newton(f_zeroder_root, x0=[0.5]*10, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# doesn't apply to halley
def test_gh_8881():
r"""Test that Halley's method realizes that the 2nd order adjustment
is too big and drops off to the 1st order adjustment."""
n = 9
def f(x):
return power(x, 1.0/n) - power(n, 1.0/n)
def fp(x):
return power(x, (1.0-n)/n)/n
def fpp(x):
return power(x, (1.0-2*n)/n) * (1.0/n) * (1.0-n)/n
x0 = 0.1
# The root is at x=9.
# The function has positive slope, x0 < root.
# Newton succeeds in 8 iterations
rt, r = newton(f, x0, fprime=fp, full_output=True)
assert(r.converged)
# Before the Issue 8881/PR 8882, halley would send x in the wrong direction.
# Check that it now succeeds.
rt, r = newton(f, x0, fprime=fp, fprime2=fpp, full_output=True)
assert(r.converged)