Mercurial > forge
view main/optim/src/samin.cc @ 9930:d30cfca46e8a octave-forge
optim: upgrade license to GPLv3+ and mention on DESCRIPTION the other package licenses
author | carandraug |
---|---|
date | Fri, 30 Mar 2012 15:14:48 +0000 |
parents | 5758bf7f1b2b |
children |
line wrap: on
line source
// Copyright (C) 2004, 2006 Michael Creel <michael.creel@uab.es> // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software // Foundation; either version 3 of the License, or (at your option) any later // version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more // details. // // You should have received a copy of the GNU General Public License along with // this program; if not, see <http://www.gnu.org/licenses/>. // References: // // The code follows the article // Goffe, William L. (1996) "SIMANN: A Global Optimization Algorithm // using Simulated Annealing " Studies in Nonlinear Dynamics & Econometrics // Oct96, Vol. 1 Issue 3. // // The code uses the same names for control variables, // for the most part. A notable difference is that the initial // temperature is found automatically to ensure that the active // bounds when the temperature begins to reduce cover the entire // parameter space (defined as a n-dimensional // rectangle that is the Cartesian product of the // (lb_i, ub_i), i = 1,2,..n // // Also of note: // Corana et. al., (1987) "Minimizing Multimodal Functions of Continuous // Variables with the "Simulated Annealing" Algorithm", // ACM Transactions on Mathematical Software, V. 13, N. 3. // // Goffe, et. al. (1994) "Global Optimization of Statistical Functions // with Simulated Annealing", Journal of Econometrics, // V. 60, N. 1/2. #include <oct.h> #include <octave/parse.h> #include <octave/Cell.h> #include <octave/lo-mappers.h> #include <octave/oct-rand.h> #include <float.h> #include "error.h" // define argument checks static bool any_bad_argument(const octave_value_list& args) { // objective function name is a string? if (!args(0).is_string()) { error("samin: first argument must be string holding objective function name"); return true; } // are function arguments contained in a cell? if (!args(1).is_cell()) { error("samin: second argument must cell array of function arguments"); return true; } // is control a cell? Cell control (args(2).cell_value()); if (error_state) { error("samin: third argument must cell array of algorithm controls"); return true; } // does control have proper number of elements? if (!(control.length() == 11)) { error("samin: third argument must be a cell array with 11 elements"); return true; } // now check type of each element of control if (!(control(0).is_real_matrix()) && !(control(0).is_real_scalar())) { error("samin: 1st element of controls must be LB: a vector of lower bounds"); return true; } if ((control(0).is_real_matrix()) && (control(0).columns() != 1)) { error("samin: 1st element of controls must be LB: a vector of lower bounds"); return true; } if (!(control(1).is_real_matrix()) && !(control(1).is_real_scalar())) { error("samin: 1st element of controls must be UB: a vector of lower bounds"); return true; } if ((control(1).is_real_matrix()) && (control(1).columns() != 1)) { error("samin: 2nd element of controls must be UB: a vector of lower bounds"); return true; } int tmp = control(2).int_value(); if (error_state || tmp < 1) { error("samin: 3rd element of controls must be NT: positive integer\n\ loops per temperature reduction"); return true; } tmp = control(3).int_value(); if (error_state || tmp < 1) { error("samin: 4th element of controls must be NS: positive integer\n\ loops per stepsize adjustment"); return true; } double tmp2 = control(4).double_value(); if (error_state || tmp < 0) { error("samin: 5th element of controls must be RT:\n\ temperature reduction factor, RT > 0"); return true; } tmp2 = control(5).double_value(); if (error_state || tmp < 0) { error("samin: 6th element of controls must be integer MAXEVALS > 0 "); return true; } tmp = control(6).int_value(); if (error_state || tmp < 0) { error("samin: 7th element of controls must be NEPS: positive integer\n\ number of final obj. values that must be within EPS of eachother "); return true; } tmp2 = control(7).double_value();if (error_state || tmp2 < 0) { error("samin: 8th element of controls must must be FUNCTOL (> 0)\n\ used to compare the last NEPS obj values for convergence test"); return true; } tmp2 = control(8).double_value(); if (error_state || tmp2 < 0) { error("samin: 9th element of controls must must be PARAMTOL (> 0)\n\ used to compare the last NEPS obj values for convergence test"); return true; } tmp = control(9).int_value(); if (error_state || tmp < 0 || tmp > 2) { error("samin: 9th element of controls must be VERBOSITY (0, 1, or 2)"); return true; } tmp = control(10).int_value(); if (error_state || tmp < 0) { error("samin: 10th element of controls must be MINARG (integer)\n\ position of argument to minimize wrt"); return true; } // make sure that minarg points to an existing element if ((tmp > args(1).length())||(tmp < 1)) { error("bfgsmin: 4th argument must be a positive integer that indicates \n\ which of the elements of the second argument is the one minimization is over"); return true; } return false; } //-------------- The annealing algorithm -------------- DEFUN_DLD(samin, args, , "samin: simulated annealing minimization of a function. See samin_example.m\n\ \n\ usage: [x, obj, convergence, details] = samin(\"f\", {args}, {control})\n\ \n\ Arguments:\n\ * \"f\": function name (string)\n\ * {args}: a cell array that holds all arguments of the function,\n\ * {control}: a cell array with 11 elements\n\ * LB - vector of lower bounds\n\ * UB - vector of upper bounds\n\ * nt - integer: # of iterations between temperature reductions\n\ * ns - integer: # of iterations between bounds adjustments\n\ * rt - (0 < rt <1): temperature reduction factor\n\ * maxevals - integer: limit on function evaluations\n\ * neps - integer: number of values final result is compared to\n\ * functol - (> 0): the required tolerance level for function value\n\ comparisons\n\ * paramtol - (> 0): the required tolerance level for parameters\n\ * verbosity - scalar: 0, 1, or 2.\n\ * 0 = no screen output\n\ * 1 = only final results to screen\n\ * 2 = summary every temperature change\n\ * minarg - integer: which of function args is minimization over?\n\ \n\ Returns:\n\ * x: the minimizer\n\ * obj: the value of f() at x\n\ * convergence:\n\ 0 if no convergence within maxevals function evaluations\n\ 1 if normal convergence to a point interior to the parameter space\n\ 2 if convergence to point very near bounds of parameter space\n\ (suggest re-running with looser bounds)\n\ * details: a px3 matrix. p is the number of times improvements were found.\n\ The columns record information at the time an improvement was found\n\ * first: cumulative number of function evaluations\n\ * second: temperature\n\ * third: function value\n\ \n\ Example: see samin_example\n\ ") { int nargin = args.length(); if (!(nargin == 3)) { error("samin: you must supply 3 arguments"); return octave_value_list(); } // check the arguments if (any_bad_argument(args)) return octave_value_list(); std::string obj_fn (args(0).string_value()); Cell f_args_cell = args(1).cell_value (); // args to obj fn come in as a cell to allow arbitrary number Cell control (args(2).cell_value()); octave_value_list f_args; octave_value_list f_return; // holder for feval returns int m, i, j, k, h, n, nacc, func_evals; int nup, nrej, nnew, ndown, lnobds; int converge, test, coverage_ok; // user provided controls const ColumnVector lb (control(0).column_vector_value()); const ColumnVector ub (control(1).column_vector_value()); const int nt (control(2).int_value()); const int ns (control(3).int_value()); const double rt (control(4).double_value()); const int maxevals (control(5).int_value()); const int neps (control(6).int_value()); const double functol (control(7).double_value()); const double paramtol (control(8).double_value()); const int verbosity (control(9).int_value()); const int minarg (control(10).int_value()); // type checking for minimization parameter done here, since we don't know minarg // until now if (!(f_args_cell(minarg - 1).is_real_matrix() || (f_args_cell(minarg - 1).is_real_scalar()))) { error("samin: minimization must be with respect to a column vector"); return octave_value_list(); } if ((f_args_cell(minarg - 1).is_real_matrix()) && (f_args_cell(minarg - 1).columns() != 1)) { error("samin: minimization must be with respect to a column vector"); return octave_value_list(); } double f, fp, p, pp, fopt, rand_draw, ratio, t; Matrix details(1,3); // record function evaluations, temperatures and function values RowVector info(3); // copy cell contents over to octave_value_list to use feval() k = f_args_cell.length(); f_args(k); // resize only once for (i = 0; i<k; i++) f_args(i) = f_args_cell(i); ColumnVector x = f_args(minarg - 1).column_vector_value(); ColumnVector bounds = ub - lb; n = x.rows(); ColumnVector xopt = x; ColumnVector xp(n); ColumnVector nacp(n); // Set initial values nacc = 0; // total accepted trials t = 1000.0; // temperature - will initially rise or fall to cover parameter space. Then it will fall converge = 0; // convergence indicator 0 (failure), 1 (normal success), or 2 (convergence but near bounds) coverage_ok = 0; // has parameter space been covered? When turns to 1, temperature starts to fall // most recent values, to compare to when checking convergend ColumnVector fstar(neps,1); fstar.fill(DBL_MAX); octave_rand::distribution("uniform"); // we'll be using draws from U(0,1) // check for out-of-bounds starting values for(i = 0; i < n; i++) { if(( x(i) > ub(i)) || (x(i) < lb(i))) { error("samin: initial parameter %d out of bounds", i+1); return octave_value_list(); } } // Initial obj_value f_return = feval(obj_fn, f_args); f = f_return(0).double_value(); fopt = f; // give it something to compare to func_evals = 0; // total function evaluations (limited by maxeval) details(0,0) = func_evals; details(0,1) = t; details(0,2) = fopt; // main loop, first increase temperature until parameter space covered, then reduce until convergence while(converge==0) { // statistics to report at each temp change, set back to zero nup = 0; nrej = 0; nnew = 0; ndown = 0; lnobds = 0; // repeat nt times then adjust temperature for(m = 0;m < nt;m++) { // repeat ns times, then adjust bounds for(j = 0;j < ns;j++) { // generate new point by taking last and adding a random value // to each of elements, in turn for(h = 0;h < n;h++) { // new Sept 2011, if bounds are same, skip the search for that vbl. Allows restrictions without complicated programming if (lb(h) != ub(h)) { xp = x; rand_draw = octave_rand::scalar(); xp(h) = x(h) + (2.0 * rand_draw - 1.0) * bounds(h); if((xp(h) < lb(h)) || (xp(h) > ub(h))) { rand_draw = octave_rand::scalar(); // change 07-Nov-2007: avoid correlation with hitting bounds xp(h) = lb(h) + (ub(h) - lb(h)) * rand_draw; lnobds = lnobds + 1; } // Evaluate function at new point f_args(minarg - 1) = xp; f_return = feval(obj_fn, f_args); fp = f_return(0).double_value(); func_evals = func_evals + 1; // Accept the new point if the function value decreases if(fp <= f) { x = xp; f = fp; nacc = nacc + 1; // total number of acceptances nacp(h) = nacp(h) + 1; // acceptances for this parameter nup = nup + 1; // If lower than any other point, record as new optimum if(fp < fopt) { xopt = xp; fopt = fp; nnew = nnew + 1; info(0) = func_evals; info(1) = t; info(2) = fp; details = details.stack(info); } } // If the point is higher, use the Metropolis criteria to decide on // acceptance or rejection. else { p = exp(-(fp - f) / t); rand_draw = octave_rand::scalar(); if(rand_draw < p) { x = xp; f = fp; nacc = nacc + 1; nacp(h) = nacp(h) + 1; ndown = ndown + 1; } else nrej = nrej + 1; } } // If maxevals exceeded, terminate the algorithm if(func_evals >= maxevals) { if(verbosity >= 1) { printf("\n================================================\n"); printf("SAMIN results\n"); printf("NO CONVERGENCE: MAXEVALS exceeded\n"); printf("================================================\n"); printf("Convergence tolerances: Func. tol. %e Param. tol. %e\n", functol, paramtol); printf("Obj. fn. value %f\n\n", fopt); printf(" parameter search width\n"); for(i = 0; i < n; i++) printf("%20f%20f\n", xopt(i), bounds(i)); } f_return(3) = details; f_return(2) = 0; f_return(1) = fopt; f_return(0) = xopt; return octave_value_list(f_return); } } } // Adjust bounds so that approximately half of all evaluations are accepted test = 0; for(i = 0;i < n;i++) { if (lb(i) != ub(i)) { ratio = nacp(i) / ns; if(ratio > 0.6) bounds(i) = bounds(i) * (1.0 + 2.0 * (ratio - 0.6) / 0.4); else if(ratio < .4) bounds(i) = bounds(i) / (1.0 + 2.0 * ((0.4 - ratio) / 0.4)); // keep within initial bounds if(bounds(i) >= (ub(i) - lb(i))) { bounds(i) = ub(i) - lb(i); test = test + 1; } } else test = test + 1; // make sure coverage check passes for the fixed parameters } nacp.fill(0.0); // check if we cover parameter space. if we have yet to do so if (!coverage_ok) coverage_ok = (test == n); } // intermediate output, if desired if(verbosity == 2) { printf("\nsamin: intermediate results before next temperature change\n"); printf("\ntemperature %e", t); printf("\ncurrent best function value %f", fopt); printf("\ntotal evaluations so far %d", func_evals); printf("\ntotal moves since last temperature reduction %d", nup + ndown + nrej); printf("\ndownhill %d", nup); printf("\naccepted uphill %d", ndown); printf("\nrejected uphill %d", nrej); printf("\nout of bounds trials %d", lnobds); printf("\nnew minima this temperature %d", nnew); printf("\n\n parameter search width\n"); for(i = 0; i < n; i++) printf("%20f%20f\n", xopt(i), bounds(i)); printf("\n"); } // Check for convergence, if we have covered the parameter space if (coverage_ok) { // last value close enough to last neps values? fstar(0) = f; test = 0; for (i = 1; i < neps; i++) test = test + fabs(f - fstar(i)) > functol; test = (test > 0); // if different from zero, function conv. has failed // last value close enough to overall best? if (((fopt - f) <= functol) && (!test)) { // check for bound narrow enough for parameter convergence for (i = 0;i < n;i++) { if (bounds(i) > paramtol) { converge = 0; // no conv. if bounds too wide break; } else converge = 1; } } // check if optimal point is near boundary of parameter space, and change convergence message if so if (converge) if (lnobds > 0) converge = 2; // Like to see the final results? if (converge > 0) { if (verbosity >= 1) { printf("\n================================================\n"); printf("SAMIN results\n\n"); if (converge == 1) printf("==> Normal convergence <==\n\n"); if (converge == 2) { printf("==> WARNING <==: Last point satisfies convergence criteria,\n"); printf("but is near boundary of parameter space.\n"); printf("%d out of %d evaluations were out-of-bounds in the last round.\n", lnobds, (nup+ndown+nrej)); printf("Expand bounds and re-run, unless this is a constrained minimization.\n\n"); } printf("Convergence tolerances:\nFunction: %e\nParameters: %e\n", functol, paramtol); printf("\nObjective function value at minimum: %f\n\n", fopt); printf(" parameter search width\n"); for(i = 0; i < n; i++) printf("%20f%20f\n", xopt(i), bounds(i)); printf("================================================\n"); } f_return(3) = details; f_return(2) = converge; f_return(1) = fopt; f_return(0) = xopt; return f_return; // this breaks out, if we get here } // Reduce temperature, record current function value in the // list of last "neps" values, and loop again t = rt * t; for(i = neps-1; i > 0; i--) fstar(i) = fstar(i-1); f = fopt; x = xopt; } else { // coverage not ok - increase temperature quickly to expand search area, to cover parameter space t = t*t; for(i = neps-1; i > 0; i--) fstar(i) = fstar(i-1); f = fopt; x = xopt; } } }