demo + utils venv
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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)
|
||||
+540
@@ -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
|
||||
+365
@@ -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))
|
||||
+239
@@ -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)
|
||||
|
||||
+270
@@ -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)
|
||||
+580
@@ -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)))
|
||||
+219
@@ -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']
|
||||
|
||||
+617
@@ -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'])
|
||||
+357
@@ -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)
|
||||
|
||||
+173
@@ -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)
|
||||
Reference in New Issue
Block a user