Permalink
Browse files

Merge pull request #329 from dlax/enh/tnc-callback

optimize: add callback for TNC (part of ticket #1538)
  • Loading branch information...
2 parents 141ebeb + 00f64f4 commit c4dcc00f953ec91c2c33143a4c37d4cafc375b7d @dlax dlax committed Oct 22, 2012
@@ -39,6 +39,9 @@ count_neighbors and sparse_distance_matrix) are between 200 and 1000 times
faster in cKDTree than in KDTree. With very minor caveats, cKDTree has
exactly the same interface as KDTree, and can be used as a drop-in replacement.
+``scipy.optimize`` improvements
+-------------------------------
+A callback mechanism was added to L-BFGS-B and TNC minimization solvers.
Deprecated features
===================
@@ -303,7 +303,7 @@ def minimize(fun, x0, args=(), method='BFGS', jac=None, hess=None,
warn('Method %s cannot handle bounds.' % method,
RuntimeWarning)
# - callback
- if (meth in ['anneal', 'tnc', 'cobyla', 'slsqp'] and
+ if (meth in ['anneal', 'cobyla', 'slsqp'] and
callback is not None):
warn('Method %s does not support callback.' % method,
RuntimeWarning)
@@ -351,7 +351,8 @@ def minimize(fun, x0, args=(), method='BFGS', jac=None, hess=None,
return _minimize_lbfgsb(fun, x0, args, jac, bounds,
callback=callback, **options)
elif meth == 'tnc':
- return _minimize_tnc(fun, x0, args, jac, bounds, **options)
+ return _minimize_tnc(fun, x0, args, jac, bounds, callback=callback,
+ **options)
elif meth == 'cobyla':
return _minimize_cobyla(fun, x0, args, constraints, **options)
elif meth == 'slsqp':
@@ -720,11 +720,13 @@ def test_minimize_tnc1(self):
"""Minimize, method=TNC, 1"""
x0, bnds = [-2, 1], ([-np.inf, None],[-1.5, None])
xopt = [1, 1]
+ iterx = [] # to test callback
- 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)
+ 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):
"""Minimize, method=TNC, 1b (approx gradient)"""
View
@@ -83,7 +83,7 @@ def fmin_tnc(func, x0, fprime=None, args=(), approx_grad=0,
bounds=None, epsilon=1e-8, scale=None, offset=None,
messages=MSG_ALL, maxCGit=-1, maxfun=None, eta=-1,
stepmx=0, accuracy=0, fmin=0, ftol=-1, xtol=-1, pgtol=-1,
- rescale=-1, disp=None):
+ rescale=-1, disp=None, callback=None):
"""
Minimize a function with variables subject to bounds, using
gradient information in a truncated Newton algorithm. This
@@ -169,6 +169,9 @@ def fmin_tnc(func, x0, fprime=None, args=(), approx_grad=0,
Scaling factor (in log10) used to trigger f value
rescaling. If 0, rescale at each iteration. If a large
value, never rescale. If < 0, rescale is set to 1.3.
+ callback : callable, optional
+ Called after each iteration, as callback(xk), where xk is the
+ current parameter vector.
Returns
-------
@@ -254,15 +257,15 @@ def fmin_tnc(func, x0, fprime=None, args=(), approx_grad=0,
'rescale': rescale,
'disp': False}
- res = _minimize_tnc(fun, x0, args, jac, bounds, **opts)
+ res = _minimize_tnc(fun, x0, args, jac, bounds, callback=callback, **opts)
return res['x'], res['nfev'], res['status']
def _minimize_tnc(fun, x0, args=(), jac=None, bounds=None,
eps=1e-8, scale=None, offset=None, mesg_num=None,
maxCGit=-1, maxiter=None, eta=-1, stepmx=0, accuracy=0,
minfev=0, ftol=-1, xtol=-1, gtol=-1, rescale=-1, disp=False,
- **unknown_options):
+ callback=None, **unknown_options):
"""
Minimize a scalar function of one or more variables using a truncated
Newton (TNC) algorithm.
@@ -386,14 +389,15 @@ def func_and_grad(x):
if maxfun is None:
maxfun = max(100, 10*len(x0))
- rc, nf, x = moduleTNC.minimize(func_and_grad, x0, low, up, scale, offset,
- messages, maxCGit, maxfun, eta, stepmx, accuracy,
- fmin, ftol, xtol, pgtol, rescale)
+ rc, nf, nit, x = moduleTNC.minimize(func_and_grad, x0, low, up, scale,
+ offset, messages, maxCGit, maxfun,
+ eta, stepmx, accuracy, fmin, ftol,
+ xtol, pgtol, rescale, callback)
xopt = array(x)
funv, jacv = func_and_grad(xopt)
- return Result(x=xopt, fun=funv, jac=jacv, nfev=nf, status=rc,
+ return Result(x=xopt, fun=funv, jac=jacv, nfev=nf, nit=nit, status=rc,
message=RCSTRINGS[rc], success=(-1 < rc < 3))
if __name__ == '__main__':
@@ -34,7 +34,7 @@ int main(int argc, char **argv)
rc = tnc(2, x, &f, g, function, NULL, low, up, NULL, NULL, TNC_MSG_ALL,
maxCGit, maxnfeval, eta, stepmx, accuracy, fmin, ftol, xtol, pgtol,
- rescale, &nfeval);
+ rescale, &nfeval, NULL);
printf("After %d function evaluations, TNC returned:\n%s\n", nfeval,
tnc_rc_string[rc - TNC_MINRC]);
@@ -36,6 +36,7 @@ static char const rcsid[] =
typedef struct _pytnc_state
{
PyObject *py_function;
+ PyObject *py_callback;
int n;
int failed;
} pytnc_state;
@@ -177,18 +178,37 @@ static int function(double x[], double *f, double g[], void *state)
return 1;
}
+static void callback(double x[], void *state)
+{
+ PyObject *py_list, *arglist, *result = NULL;
+ pytnc_state *py_state = (pytnc_state *)state;
+
+ py_list = PyDoubleArray_AsList(py_state->n, x);
+ if (py_list == NULL)
+ {
+ PyErr_SetString(PyExc_MemoryError, "tnc: memory allocation failed.");
+ }
+
+ arglist = Py_BuildValue("(N)", py_list);
+ result = PyEval_CallObject(py_state->py_callback, arglist);
+ Py_DECREF(arglist);
+ Py_XDECREF(result);
+}
+
PyObject *moduleTNC_minimize(PyObject *self, PyObject *args)
{
PyObject *py_x0, *py_low, *py_up, *py_list, *py_scale, *py_offset;
PyObject *py_function = NULL;
+ PyObject *py_callback = NULL;
pytnc_state py_state;
int n, n1, n2, n3, n4;
+ tnc_callback *callback_function = NULL;
- int rc, msg, maxCGit, maxnfeval, nfeval = 0;
+ int rc, msg, maxCGit, maxnfeval, nfeval = 0, niter = 0;
double *x, *low, *up, *scale = NULL, *offset = NULL;
double f, eta, stepmx, accuracy, fmin, ftol, xtol, pgtol, rescale;
- if (!PyArg_ParseTuple(args, "OO!O!O!O!O!iiidddddddd",
+ if (!PyArg_ParseTuple(args, "OO!O!O!O!O!iiiddddddddO",
&py_function,
&PyList_Type, &py_x0,
&PyList_Type, &py_low,
@@ -197,7 +217,8 @@ PyObject *moduleTNC_minimize(PyObject *self, PyObject *args)
&PyList_Type, &py_offset,
&msg, &maxCGit, &maxnfeval, &eta, &stepmx, &accuracy, &fmin, &ftol,
&xtol, &pgtol,
- &rescale
+ &rescale,
+ &py_callback
))
return NULL;
@@ -263,11 +284,25 @@ PyObject *moduleTNC_minimize(PyObject *self, PyObject *args)
Py_INCREF(py_function);
+ if (py_callback != Py_None)
+ {
+ if (!PyCallable_Check(py_callback))
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "tnc: callback must be callable or None.");
+ return NULL;
+ }
+ py_state.py_callback = py_callback;
+ Py_INCREF(py_callback);
+ callback_function = callback;
+ }
+
rc = tnc(n, x, &f, NULL, function, &py_state, low, up, scale, offset, msg,
maxCGit, maxnfeval, eta, stepmx, accuracy, fmin, ftol, xtol, pgtol, rescale,
- &nfeval);
+ &nfeval, &niter, callback_function);
Py_DECREF(py_function);
+ if (py_callback != Py_None) Py_DECREF(py_callback);
if (low) free(low);
if (up) free(up);
@@ -295,7 +330,7 @@ PyObject *moduleTNC_minimize(PyObject *self, PyObject *args)
return NULL;
}
- return Py_BuildValue("(iiN)", rc, nfeval, py_list);;
+ return Py_BuildValue("(iiiN)", rc, nfeval, niter, py_list);;
}
static PyMethodDef moduleTNC_methods[] =
View
@@ -112,9 +112,10 @@ static tnc_rc tnc_minimize(int n, double x[], double *f, double g[],
tnc_function *function, void *state,
double xscale[], double xoffset[], double *fscale,
double low[], double up[], tnc_message messages,
- int maxCGit, int maxnfeval, int *nfeval,
+ int maxCGit, int maxnfeval, int *nfeval, int *niter,
double eta, double stepmx, double accuracy,
- double fmin, double ftol, double xtol, double pgtol, double rescale);
+ double fmin, double ftol, double xtol, double pgtol, double rescale,
+ tnc_callback *callback);
static getptc_rc getptcInit(double *reltol, double *abstol, double tnytol,
double eta, double rmu, double xbnd,
@@ -235,7 +236,7 @@ extern int tnc(int n, double x[], double *f, double g[], tnc_function *function,
void *state, double low[], double up[], double scale[], double offset[],
int messages, int maxCGit, int maxnfeval, double eta, double stepmx,
double accuracy, double fmin, double ftol, double xtol, double pgtol,
- double rescale, int *nfeval)
+ double rescale, int *nfeval, int *niter, tnc_callback *callback)
{
int rc, frc, i, nc, nfeval_local,
free_low = TNC_FALSE, free_up = TNC_FALSE,
@@ -403,8 +404,8 @@ extern int tnc(int n, double x[], double *f, double g[], tnc_function *function,
/* Optimisation */
rc = tnc_minimize(n, x, f, g, function, state,
xscale, xoffset, &fscale, low, up, messages,
- maxCGit, maxnfeval, nfeval, eta, stepmx, accuracy, fmin, ftol, xtol, pgtol,
- rescale);
+ maxCGit, maxnfeval, nfeval, niter, eta, stepmx, accuracy, fmin, ftol,
+ xtol, pgtol, rescale, callback);
cleanup:
if (messages & TNC_MSG_EXIT)
@@ -500,20 +501,22 @@ static tnc_rc tnc_minimize(int n, double x[],
double *f, double gfull[], tnc_function *function, void *state,
double xscale[], double xoffset[], double *fscale,
double low[], double up[], tnc_message messages,
- int maxCGit, int maxnfeval, int *nfeval, double eta, double stepmx,
- double accuracy, double fmin, double ftol, double xtol, double pgtol,
- double rescale)
+ int maxCGit, int maxnfeval, int *nfeval, int *niter, double eta, double
+ stepmx, double accuracy, double fmin, double ftol, double xtol, double
+ pgtol, double rescale, tnc_callback *callback)
{
double fLastReset, difnew, epsmch, epsred, oldgtp,
difold, oldf, xnorm, newscale,
gnorm, ustpmax, fLastConstraint, spe, yrsr, yksk,
*temp = NULL, *sk = NULL, *yk = NULL, *diagb = NULL, *sr = NULL,
*yr = NULL, *oldg = NULL, *pk = NULL, *g = NULL;
double alpha = 0.0; /* Default unused value */
- int i, icycle, niter = 0, oldnfeval, *pivot = NULL, frc;
+ int i, icycle, oldnfeval, *pivot = NULL, frc;
logical lreset, newcon, upd1, remcon;
tnc_rc rc = TNC_ENOMEM; /* Default error */
+ *niter = 0;
+
/* Allocate temporary vectors */
oldg = malloc(sizeof(*oldg)*n);
if (oldg == NULL) goto cleanup;
@@ -578,7 +581,7 @@ static tnc_rc tnc_minimize(int n, double x[],
if (messages & TNC_MSG_ITER) fprintf(stderr,
" NIT NF F GTG\n");
if (messages & TNC_MSG_ITER) printCurrentIteration(n, *f / *fscale, gfull,
- niter, *nfeval, pivot);
+ *niter, *nfeval, pivot);
/* Set the diagonal of the approximate hessian to unity. */
for (i = 0; i < n; i++) diagb[i] = 1.0;
@@ -757,7 +760,15 @@ static tnc_rc tnc_minimize(int n, double x[],
fLastConstraint = *f;
}
- niter++;
+ (*niter)++;
+
+ /* Invoke the callback function */
+ if (callback)
+ {
+ unscalex(n, x, xscale, xoffset);
+ callback(x, state);
+ scalex(n, x, xscale, xoffset);
+ }
/* Set up parameters used in convergence and resetting tests */
difold = difnew;
@@ -814,7 +825,7 @@ static tnc_rc tnc_minimize(int n, double x[],
project(n, g, pivot);
if (messages & TNC_MSG_ITER) printCurrentIteration(n, *f / *fscale, gfull,
- niter, *nfeval, pivot);
+ *niter, *nfeval, pivot);
/* Compute the change in the iterates and the corresponding change in the
gradients */
@@ -842,7 +853,7 @@ static tnc_rc tnc_minimize(int n, double x[],
}
if (messages & TNC_MSG_ITER) printCurrentIteration(n, *f / *fscale, gfull,
- niter, *nfeval, pivot);
+ *niter, *nfeval, pivot);
/* Unscaling */
unscalex(n, x, xscale, xoffset);
View
@@ -107,6 +107,12 @@ extern char *tnc_rc_string[11];
typedef int tnc_function(double x[], double *f, double g[], void *state);
/*
+ * A callback function accepting x as input parameter along with the state
+ * pointer.
+ */
+typedef void tnc_callback(double x[], void *state);
+
+/*
* tnc : minimize a function with variables subject to bounds, using
* gradient information.
*
@@ -165,7 +171,7 @@ extern int tnc(int n, double x[], double *f, double g[],
double low[], double up[], double scale[], double offset[],
int messages, int maxCGit, int maxnfeval, double eta, double stepmx,
double accuracy, double fmin, double ftol, double xtol, double pgtol,
- double rescale, int *nfeval);
+ double rescale, int *nfeval, int *niter, tnc_callback *callback);
#ifdef __cplusplus
}

0 comments on commit c4dcc00

Please sign in to comment.