Skip to content

Commit

Permalink
Merge pull request #217 from pv/minimize-opts
Browse files Browse the repository at this point in the history
Tweaks to minimize(), minimize_scalar() and root() behavior.
  • Loading branch information
dlax committed Jun 9, 2012
2 parents 5f0b796 + c323667 commit 060f78e
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 357 deletions.
61 changes: 40 additions & 21 deletions scipy/optimize/_minimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
from slsqp import _minimize_slsqp

def minimize(fun, x0, args=(), method='BFGS', jac=None, hess=None,
hessp=None, bounds=None, constraints=(),
options=None, callback=None):
hessp=None, bounds=None, constraints=(), tol=None,
callback=None, options=None):
"""
Minimization of scalar function of one or more variables.
Expand Down Expand Up @@ -92,6 +92,9 @@ def minimize(fun, x0, args=(), method='BFGS', jac=None, hess=None,
Equality constraint means that the constraint function result is to
be zero whereas inequality means that it is to be non-negative.
Note that COBYLA only supports inequality constraints.
tol : float, optional
Tolerance for termination. For detailed control, use solver-specific
options.
options : dict, optional
A dictionary of solver options. All methods accept the following
generic options:
Expand Down Expand Up @@ -318,34 +321,47 @@ def minimize(fun, x0, args=(), method='BFGS', jac=None, hess=None,
else:
jac = None

# set default tolerances
if tol is not None:
options = dict(options)
if meth in ['nelder-mead', 'newton-cg', 'powell', 'tnc']:
options.setdefault('xtol', tol)
if meth in ['nelder-mead', 'powell', 'anneal', 'l-bfgs-b', 'tnc',
'slsqp']:
options.setdefault('ftol', tol)
if meth in ['bfgs', 'cg', 'l-bfgs-b', 'tnc']:
options.setdefault('gtol', tol)
if meth in ['cobyla']:
options.setdefault('tol', tol)

if meth == 'nelder-mead':
return _minimize_neldermead(fun, x0, args, options, callback)
return _minimize_neldermead(fun, x0, args, callback, **options)
elif meth == 'powell':
return _minimize_powell(fun, x0, args, options, callback)
return _minimize_powell(fun, x0, args, callback, **options)
elif meth == 'cg':
return _minimize_cg(fun, x0, args, jac, options, callback)
return _minimize_cg(fun, x0, args, jac, callback, **options)
elif meth == 'bfgs':
return _minimize_bfgs(fun, x0, args, jac, options, callback)
return _minimize_bfgs(fun, x0, args, jac, callback, **options)
elif meth == 'newton-cg':
return _minimize_newtoncg(fun, x0, args, jac, hess, hessp, options,
callback)
return _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback,
**options)
elif meth == 'anneal':
return _minimize_anneal(fun, x0, args, options)
return _minimize_anneal(fun, x0, args, **options)
elif meth == 'l-bfgs-b':
return _minimize_lbfgsb(fun, x0, args, jac, bounds, options)
return _minimize_lbfgsb(fun, x0, args, jac, bounds, **options)
elif meth == 'tnc':
return _minimize_tnc(fun, x0, args, jac, bounds, options)
return _minimize_tnc(fun, x0, args, jac, bounds, **options)
elif meth == 'cobyla':
return _minimize_cobyla(fun, x0, args, constraints, options)
return _minimize_cobyla(fun, x0, args, constraints, **options)
elif meth == 'slsqp':
return _minimize_slsqp(fun, x0, args, jac, bounds,
constraints, options)
constraints, **options)
else:
raise ValueError('Unknown solver %s' % method)


def minimize_scalar(fun, bracket=None, bounds=None, args=(),
method='brent', options=None):
method='brent', tol=None, options=None):
"""
Minimization of scalar function of one variable.
Expand Down Expand Up @@ -374,14 +390,14 @@ def minimize_scalar(fun, bracket=None, bounds=None, args=(),
- 'Brent'
- 'Bounded'
- 'Golden'
tol : float, optional
Tolerance for termination. For detailed control, use solver-specific
options.
options : dict, optional
A dictionary of solver options.
xtol : float
Relative error in solution `xopt` acceptable for
convergence.
ftol : float
Relative error in ``fun(xopt)`` acceptable for convergence.
maxiter : int
Maximum number of iterations to perform.
disp : bool
Expand Down Expand Up @@ -443,15 +459,18 @@ def minimize_scalar(fun, bracket=None, bounds=None, args=(),
if options is None:
options = {}

if tol is not None:
options = dict(options)
options.setdefault('xtol', tol)

if meth == 'brent':
return _minimize_scalar_brent(fun, bracket, args, options)
return _minimize_scalar_brent(fun, bracket, args, **options)
elif meth == 'bounded':
if bounds is None:
raise ValueError('The `bounds` parameter is mandatory for '
'method `bounded`.')
return _minimize_scalar_bounded(fun, bounds, args, options)
return _minimize_scalar_bounded(fun, bounds, args, **options)
elif meth == 'golden':
return _minimize_scalar_golden(fun, bracket, args, options)
return _minimize_scalar_golden(fun, bracket, args, **options)
else:
raise ValueError('Unknown solver %s' % method)

158 changes: 91 additions & 67 deletions scipy/optimize/_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@

__all__ = ['root']

import numpy as np

from warnings import warn

from optimize import MemoizeJac, Result
from optimize import MemoizeJac, Result, _check_unknown_options
from minpack import _root_hybr, leastsq
import nonlin

def root(fun, x0, args=(), method='hybr', jac=None, options=None,
callback=None):
def root(fun, x0, args=(), method='hybr', jac=None, tol=None, callback=None,
options=None):
"""
Find a root of a vector function.
Expand Down Expand Up @@ -48,13 +50,16 @@ def root(fun, x0, args=(), method='hybr', jac=None, options=None,
Jacobian will be estimated numerically.
`jac` can also be a callable returning the Jacobian of `fun`. In
this case, it must accept the same arguments as `fun`.
options : dict, optional
A dictionary of solver options. E.g. `xtol` or `maxiter`, see
``show_options('root', method)`` for details.
tol : float, optional
Tolerance for termination. For detailed control, use solver-specific
options.
callback : function, optional
Optional callback function. It is called on every iteration as
``callback(x, f)`` where `x` is the current solution and `f`
the corresponding residual. For all methods but 'hybr' and 'lm'.
options : dict, optional
A dictionary of solver options. E.g. `xtol` or `maxiter`, see
``show_options('root', method)`` for details.
Returns
-------
Expand Down Expand Up @@ -147,76 +152,95 @@ def root(fun, x0, args=(), method='hybr', jac=None, options=None,
else:
jac = None

# set default tolerances
if tol is not None:
options = dict(options)
if meth in ('hybr', 'lm'):
options.setdefault('xtol', tol)
elif meth in ('broyden1', 'broyden2', 'anderson', 'linearmixing',
'diagbroyden', 'excitingmixing', 'krylov'):
options.setdefault('xtol', tol)
options.setdefault('xatol', np.inf)
options.setdefault('ftol', np.inf)
options.setdefault('fatol', np.inf)

if meth == 'hybr':
sol = _root_hybr(fun, x0, args=args, jac=jac, options=options)
sol = _root_hybr(fun, x0, args=args, jac=jac, **options)
elif meth == 'lm':
col_deriv = options.get('col_deriv', 0)
xtol = options.get('xtol', 1.49012e-08)
ftol = options.get('ftol', 1.49012e-08)
gtol = options.get('gtol', 0.0)
maxfev = options.get('maxfev', 0)
epsfcn = options.get('epsfcn', 0.0)
factor = options.get('factor', 100)
diag = options.get('diag', None)
x, cov_x, info, msg, ier = leastsq(fun, x0, args=args, Dfun=jac,
full_output=True,
col_deriv=col_deriv, xtol=xtol,
ftol=ftol, gtol=gtol,
maxfev=maxfev, epsfcn=epsfcn,
factor=factor, diag=diag)
sol = Result(x=x, message=msg, status=ier,
success=ier in (1, 2, 3, 4), cov_x=cov_x,
fun=info.pop('fvec'))
sol.update(info)
sol = _root_leastsq(fun, x0, args=args, jac=jac, **options)
elif meth in ('broyden1', 'broyden2', 'anderson', 'linearmixing',
'diagbroyden', 'excitingmixing', 'krylov'):
if jac is not None:
warn('Method %s does not use the jacobian (jac).' % method,
RuntimeWarning)

jacobian = {'broyden1': nonlin.BroydenFirst,
'broyden2': nonlin.BroydenSecond,
'anderson': nonlin.Anderson,
'linearmixing': nonlin.LinearMixing,
'diagbroyden': nonlin.DiagBroyden,
'excitingmixing': nonlin.ExcitingMixing,
'krylov': nonlin.KrylovJacobian
}[meth]

nit = options.get('nit')
verbose = options.get('disp', False)
maxiter = options.get('maxiter')
f_tol = options.get('ftol')
f_rtol = options.get('frtol')
x_tol = options.get('xtol')
x_rtol = options.get('xrtol')
tol_norm = options.get('tol_norm')
line_search = options.get('line_search', 'armijo')

jac_opts = options.get('jac_options', dict())

if args:
def f(x):
if jac == True:
r = fun(x, *args)[0]
else:
r = fun(x, *args)
return r
else:
f = fun

x, info = nonlin.nonlin_solve(f, x0, jacobian=jacobian(**jac_opts),
iter=nit, verbose=verbose,
maxiter=maxiter, f_tol=f_tol,
f_rtol=f_rtol, x_tol=x_tol,
x_rtol=x_rtol, tol_norm=tol_norm,
line_search=line_search,
callback=callback, full_output=True,
raise_exception=False)
sol = Result(x=x)
sol.update(info)
sol = _root_nonlin_solve(fun, x0, args=args, jac=jac,
_method=meth, _callback=callback,
**options)
else:
raise ValueError('Unknown solver %s' % method)

return sol

def _root_leastsq(func, x0, args=(), jac=None,
col_deriv=0, xtol=1.49012e-08, ftol=1.49012e-08,
gtol=0.0, maxiter=0, eps=0.0, factor=100, diag=None,
**unknown_options):
_check_unknown_options(unknown_options)
x, cov_x, info, msg, ier = leastsq(func, x0, args=args, Dfun=jac,
full_output=True,
col_deriv=col_deriv, xtol=xtol,
ftol=ftol, gtol=gtol,
maxfev=maxiter, epsfcn=eps,
factor=factor, diag=diag)
sol = Result(x=x, message=msg, status=ier,
success=ier in (1, 2, 3, 4), cov_x=cov_x,
fun=info.pop('fvec'))
sol.update(info)
return sol

def _root_nonlin_solve(func, x0, args=(), jac=None,
_callback=None, _method=None,
nit=None, disp=False, maxiter=None,
ftol=None, fatol=None, xtol=None, xatol=None,
tol_norm=None, line_search='armijo', jac_options=None,
**unknown_options):
_check_unknown_options(unknown_options)

f_tol = fatol
f_rtol = ftol
x_tol = xatol
x_rtol = xtol
verbose = disp
if jac_options is None:
jac_options = dict()

jacobian = {'broyden1': nonlin.BroydenFirst,
'broyden2': nonlin.BroydenSecond,
'anderson': nonlin.Anderson,
'linearmixing': nonlin.LinearMixing,
'diagbroyden': nonlin.DiagBroyden,
'excitingmixing': nonlin.ExcitingMixing,
'krylov': nonlin.KrylovJacobian
}[_method]

if args:
if jac == True:
def f(x):
return func(x, *args)[0]
else:
def f(x):
return func(x, *args)
else:
f = func

x, info = nonlin.nonlin_solve(f, x0, jacobian=jacobian(**jac_options),
iter=nit, verbose=verbose,
maxiter=maxiter, f_tol=f_tol,
f_rtol=f_rtol, x_tol=x_tol,
x_rtol=x_rtol, tol_norm=tol_norm,
line_search=line_search,
callback=_callback, full_output=True,
raise_exception=False)
sol = Result(x=x)
sol.update(info)
return sol
34 changes: 11 additions & 23 deletions scipy/optimize/anneal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy
from numpy import asarray, tan, exp, ones, squeeze, sign, \
all, log, sqrt, pi, shape, array, minimum, where, random
from optimize import Result
from optimize import Result, _check_unknown_options

__all__ = ['anneal']

Expand Down Expand Up @@ -303,15 +303,20 @@ def anneal(func, x0, args=(), schedule='fast', full_output=0,
'dwell' : dwell,
'disp' : disp}

res = _minimize_anneal(func, x0, args, opts)
res = _minimize_anneal(func, x0, args, **opts)

if full_output:
return res['x'], res['fun'], res['T'], res['nfev'], res['nit'], \
res['accept'], res['status']
else:
return res['x'], res['status']

def _minimize_anneal(func, x0, args=(), options=None):
def _minimize_anneal(func, x0, args=(),
schedule='fast', T0=None, Tf=1e-12, maxfev=None,
maxaccept=None, maxiter=400, boltzmann=1.0, learn_rate=0.5,
ftol=1e-6, quench=1.0, m=1.0, n=1.0, lower=-100,
upper=100, dwell=50, disp=False,
**unknown_options):
"""
Minimization of scalar function of one or more variables using the
simulated annealing algorithm.
Expand Down Expand Up @@ -350,26 +355,9 @@ def _minimize_anneal(func, x0, args=(), options=None):
This function is called by the `minimize` function with
`method=anneal`. It is not supposed to be called directly.
"""
if options is None:
options = {}
# retrieve useful options
schedule = options.get('schedule', 'fast')
T0 = options.get('T0')
Tf = options.get('Tf', 1e-12)
maxeval = options.get('maxfev')
maxaccept = options.get('maxaccept')
maxiter = options.get('maxiter', 400)
boltzmann = options.get('boltzmann', 1.0)
learn_rate = options.get('learn_rate', 0.5)
feps = options.get('ftol', 1e-6)
quench = options.get('quench', 1.0)
m = options.get('m', 1.0)
n = options.get('n', 1.0)
lower = options.get('lower', -100)
upper = options.get('upper', 100)
dwell = options.get('dwell', 50)
disp = options.get('disp', False)

_check_unknown_options(unknown_options)
maxeval = maxfev
feps = ftol

x0 = asarray(x0)
lower = asarray(lower)
Expand Down
Loading

0 comments on commit 060f78e

Please sign in to comment.