/********************************************************* Implements the simplex-simulated annealing scheme for continuous, bounded optimization described in: Cardoso, M.F., Salcedo, R.L., Feyo de Azevedo, S. The simplex-simulated annealing approach to continuous non-linear optimization. Computers chem. Engng 20(9): 1065-1080, (1996). Unconstrained Simulated Annealing code was written by Andrew Davison (andrew.davison@unic.cnrs-gif.fr). This code was modified to implement the Cardoso algorithm by Christina Weaver (Dec. 2004), christina.weaver@mssm.edu. This implementation is described in Weaver, C.M. and Wearne, S.L. The role of action potential shape and parameter constraints in optimization of compartment models. Neurocomputing 69: 1053-1057, (2006). *********************************************************/ mulfit_optimizers_append("Constrained Simulated Annealing", "MulfitSA_Rctr", 0) begintemplate MulfitSA_Rctr public prun, sa_efun, minerr, nefun, time public saveflag, showopt, verbose public estimate_inittemp public alpha, beta, gamma, lambda, ftol, start_temptr, gtol public restart_factor, cool_rate, iter_per_step, quit_temptr public savepath_name, bd_pnlty, rest_inname, use_rst, get_rstname // testing wraparound BCs public wrapfile objref wrapfile objref r, simplex, old_simplex objref simplex_errors, new_point, best_point, mean_point, wrap_point objref savepath, tl, pf, verbfile, annealpath, rest_file objref simperrors_cpy, simplex_noise, centrd, tmp_vec strdef savefit_name, cmnd, savepath_name, savetmp_name, ann_str, verbname strdef annealfit_name objref code_low, code_high // There are two restart filenames: one to read for initialization (when directed), // and one to write. Filenames may be different, but not necessarily! Be careful // of this. strdef rest_name, rest_inname // // N.B. cmw nov 04: // // "eb" corresponds to the error of the "best point" // "elo" is the lowest error of the simplex vertices // "minerr" is the value returned to the MulRunFitter wrapper // // in the noisy_simplex proc, comments were added as in // Numerical Recipes in C. proc init() { printf("Simulated Annealing + Recenter, last updated 12 Jun 06\n") pf = $o1 r = new Random() r.uniform(1e-20,1) savepath = new File() annealpath = new File() verbfile = new File() rest_file = new File() new_point = new Vector() nefun = 0 st = 0 lambda = 2 ftol = 1e-5 quit_temptr = 1e-7 gtol = ftol alpha = 1.0 beta = 2.0 gamma = 0.5 iter_per_step = 100 start_temptr = 1000 cool_rate = 0.9 restart_factor = -1 eb = 1e9 enow = 1e9 saveflag = 1 verbose = 0 sprint(savepath_name,"annealBD") sprint(rest_inname,"annealBD.rst") bd_pnlty = 1e5 set_annsched(0) // for 'complex' cooling schedule from A. Davison, use set_annsched(1) use_NE = 0 use_rst = 0 Kbound = Kbound_m2 = 1 anneal_count = 1 } /************************************************** Evaluate the error between model & target **************************************************/ func sa_efun() { local e, ttime, i // $3 = 1 if this is centroid/out-of-bounds, 0 otherwise nefun += 1 if( verbose ) { verbfile.printf("SA_EFUN : centroid/OOB = %d\n",$3) new_point.printf(verbfile) verbfile.printf("vs. sa_efun sent ") for i = 0,$1-1 { verbfile.printf("%-12.8g ",$&2[i]) } verbfile.printf("\n") verbfile.printf("vs. pf.pmlst point ") for i = 0,pf.parmlist.count-1 { verbfile.printf("%-12.8g ",pf.parmlist.object(i).val) } verbfile.printf("\n") } e = pf.efun($1, &$&2) if (!stoprun) { if (minerr == -1 || (($3 != 1) && (e < minerr))) { minerr = e if (saveflag) { ttime = startsw() - st savepath.aopen(savefit_name) lines += 1 if( verbose ) { if( $3 == 1 ) { savepath.printf("C") } savepath.printf("EF \t") } savepath.printf("%g %d %d %-12.8g ", temptr, nefun, ttime, minerr) saveval(savepath) savepath.close if( verbose ) { verbfile.printf("WROTE new_point ") new_point.printf(verbfile) verbfile.printf("vs. sa_efun sent ") for i = 0,$1-1 { verbfile.printf("%-12.8g ",$&2[i]) } verbfile.printf("\n") verbfile.printf("vs. pf.pmlst point ") for i = 0,pf.parmlist.count-1 { verbfile.printf("%-12.8g ",pf.parmlist.object(i).val) } verbfile.printf("\n") } } } } doNotify() if (verbose) { verbfile.printf("%d %14.12g ",nefun, e) for i = 0,$1-1 { verbfile.printf("%14.12g ",$&2[i]) } verbfile.printf("\n") } enow = e return e } /**************************************************************** Use the data stored in savepath.rst file to initialize the simplex, and set the starting temperature. ****************************************************************/ func init_from_restart_file() { local i, j, rst_temp, n_par, nrow, tmpval, idx rest_file.ropen(rest_inname) rst_temp = rest_file.scanvar() n_par = rest_file.scanvar() nrow = rest_file.scanvar() tmp_vec = new Vector(n_par) tl = pf.parmlist /*** first, read which variables were optimized in the last run. ***/ for i = 0, n_par-1 { tmp_vec.x[i] = rest_file.scanvar() if( tl.object(i).doarg != tmp_vec.x[i] ) { printf("Parameters optimized in restart file %s do not \ match those specified here. Abort init_restart\n", \ rest_inname) rest_file.close() if( verbose ) { verbfile.printf("Parameters optimized in restart file %s do not \ match those specified here. Abort init_restart\n", \ rest_inname) } return 0 } } /*** now, read the actual values of each of the simplex points. ***/ for j = 0, nrow-1 { idx = 0 for i = 0, n_par-1 { tmpval = rest_file.scanvar() if( ( tmp_vec.x[i] == 0 ) && ((tl.object(i).val - tmpval) > ftol) ) { printf("Value of %s = %g in restart file, != current value %g\n",\ tl.object(i).name,tmpval,tl.object(i).val) if( verbose ) { verbfile.printf("Value of %s = %g in restart file, != current value %g\n",\ tl.object(i).name,tmpval,tl.object(i).val) } rest_file.close() return 0 } else { if( tmp_vec.x[i] ) { simplex.x[j][idx] = code_value(tmpval,idx) idx += 1 } } } } rest_file.close() start_temptr = rst_temp printf("Restart file %s read; Begin at temperature %g\n",rest_inname,rst_temp) printf("Simplex initialized to: \n") simplex.printf if( verbose ) { verbfile.printf("Restart file %s read; Begin at temperature %g\n",rest_inname,rst_temp) simplex.fprintf(verbfile) } return 1 } /************************************************************** Initialize the simplex, define best_point and mean_point, and evaluate the error function at simplex points. **************************************************************/ proc initialize_simplex() { local rval, inbound, abval, i,j,bdval, did_rst abval = 0 for i = 0, new_point.size-1 { abval += abs(true_value(new_point.x[i],i)) } // initialise the simplex nparam = new_point.size() npoints = nparam + 1 simplex = new Matrix(npoints,nparam) did_rst = 0 if( use_rst ) { // Use the data stored in savepath.rst file to initialize // the simplex, and set the starting temperature. did_rst = init_from_restart_file() } // Either no restart was specified, or there was an error reading the // restart file; initialize the simplex randomly. // // NOTE: cmw, jun 06: This initialization does NOT work well when the // points are converted to log space. Initialize the points in linear // space first, then convert them (if appropriate) to log space before // they are added to the simplex. // if( !use_rst || !did_rst ) { for j = 0, nparam-1 { if( (pf.parmlist_use.object(j).low == -1e6) || \ (pf.parmlist_use.object(j).high == 1e6) ) { bdval = 2*abval } else { bdval = Kbound*(pf.parmlist_use.object(j).high-pf.parmlist_use.object(j).low) } for i = 0,npoints-1 { inbound = 0 while( !inbound ) { rval = 0.5-r.repick() simplex.x[i][j] = code_value(true_value(new_point.x[j],j) + rval*bdval,j) if( (simplex.x[i][j] >= code_low.x[j] ) && \ (simplex.x[i][j] <= code_high.x[j]) ) { inbound = 1 if( verbose ) { verbfile.printf("Set row %d, %s to %g\n",j, \ pf.parmlist_use.object(j).name,\ true_value(simplex.x[i][j],j)) } } else { if( verbose ) { verbfile.printf("Initialized row %d, %s to %g, out of range [%g, %g]\n",\ j,pf.parmlist_use.object(j).name,\ true_value(simplex.x[i][j],j),\ pf.parmlist_use.object(j).low,pf.parmlist_use.object(j).high) } } } } } } best_point = new Vector(nparam) mean_point = new Vector(nparam) centrd = new Vector(nparam) // initialise the error vector simplex_errors = new Vector(npoints) for i = 0,npoints-1 { if (stoprun) return new_point = simplex.getrow(i) if( verbose ) { verbfile.printf("INIT (OOB %d) new_point is ",out_of_bounds(new_point)) new_point.printf(verbfile,"%-12.8 ") } simplex_errors.x[i] = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) } //initialize the vector which stores the applied noise. simplex_noise = new Vector(npoints,0) } func prun() { local lines minerr = -1 restart_temptr = restart_factor * start_temptr if (saveflag) { sprint(savefit_name,"%s.tmp",savepath_name) sprint(annealfit_name,"%s.ann",savepath_name) lines = 0 } if( verbose ) { printf("Opening %s.vrb\n",savepath_name) sprint(verbname,"%s.vrb",savepath_name) verbfile.wopen(verbname) } // flag: whether the simplex has 'collapsed', and the trial number at which this occurs simplx_stop = -1 simplx_stoptrial = 0 pf.doarg_get(new_point) set_coded_bounds() nefun = 0 eb = 1e9 st = startsw() time = st if (new_point.size == 0) { minerr = pf.efun(0, &time) // time is dummy here } else { if (stoprun) {return minerr} minerr = fit_simanneal_seq() new_point = best_point.c if( verbose ) { verbfile.printf("PRUN before sa_efun, new_point = ") new_point.printf(verbfile) } minerr = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) } time = startsw() - st if (saveflag) { if( savepath.isopen ) { savepath.close } // write the final temp & params to the .tmp file, then copy data over // to the .fit file ttime = startsw() - st savepath.aopen(savefit_name) savepath.printf("%g %d %d %-12.8g ", temptr, nefun, ttime, minerr) save_best_params(savepath) savepath.close sprint(savefit_name,"%s.fit",savepath_name) savepath.aopen(savefit_name) savepath.printf("start\n") // First line of .fit file records the number of columns in each line of the data. savepath.printf("%d %d\n", lines, pf.parmlist.count + 4) savepath.close sprint(cmnd,"cat %s.tmp >> %s.fit",savepath_name,savepath_name) system(cmnd) if( annealpath.isopen ) { annealpath.close } sprint(annealfit_name,"%s.annfit",savepath_name) annealpath.aopen(annealfit_name) annealpath.printf("start\n") annealpath.printf("%d %d\n", lines, pf.parmlist.count + 4) annealpath.close sprint(cmnd,"cat %s.ann >> %s.annfit",savepath_name,savepath_name) system(cmnd) print_info_file() } printf("Final temperature: %9.5g\tnefun: %d\tFinal error: %9.5g\tat point ",temptr,nefun,eb) best_point.printf("%9.4g") if( verbose ) { verbfile.printf("Final temperature: %9.5g\tnefun: %d\tFinal error: %9.5g\tat point ",temptr,nefun,eb) best_point.printf(verbfile) verbfile.close } return minerr } proc showopt() { variable_domain(&start_temptr,0,1e12) variable_domain(&cool_rate,0,1) variable_domain(&iter_per_step,1,1e12) xpanel("") xbutton("Estimate initial temp","estimate_inittemp()") xcheckbox("Verbose output",&verbose) xcheckbox("Restart from .rst file",&use_rst) xvarlabel(rest_inname) xbutton("Change restart filename","get_rstname(rest_inname)") xlabel("Simplex parameters") xpvalue("alpha",&alpha,1) xpvalue("beta",&beta,1) xpvalue("gamma",&gamma,1) xpvalue("lambda",&lambda,1) xlabel("Simulated annealing parameters") xmenu("Select cooling schedule") xbutton("Proportional decrease","set_annsched(0)") xmenu() xvarlabel(ann_str) xpvalue("Cooling rate",&cool_rate,1) xpvalue("Start temperature",&start_temptr,1) xpvalue("Iterations per cooling step",&iter_per_step,1) xpvalue("ftol",&ftol,1) xpvalue("Norm grad tol",>ol,1) xpvalue("Restart factor",&restart_factor,1) xpanel() xpanel("") xpvalue("Current temperature",&temptr) xpvalue("Current lowest error",&elo) xpvalue("Current tolerance",&rtol) xpanel() } proc set_annsched() { ann_sched = $1 if( ann_sched == 0 ) { sprint(ann_str,"Proportional decrease") } } func noisy_simplex() { local i, e, iter, esave, gstop iter = $1 Ni = pf.parmlist_use.count * 5 Ci = Ci_avg = CiM1_avg = 0 m1 = m2 = delf = 0 nchk = Ni ndone = 1 gstop = 0 while (1) { if( verbose ) { verbfile.printf("\n\nTop of loop, ") print_simplex_info(verbfile) } if (stoprun) break // find the highest (worst), next highest and lowest (best) points // // whenever we "look" at a vertex, it gets a random thermal fluctuation. // simplex_errors stores the error of the simplex points, // plus these fluctuations. // simperrors_cpy = simplex_errors.c.sub(simplex_noise) simplex_errors = simperrors_cpy.c for i = 0, npoints-1 { // By storing the noise applied at each step, in addition to the // noise-amplified error, we are able to retain the actual function // values observed, which is crucial to the Press algorithm. Otherwise // we keep adding noise on top of itself, which is a real problem when the // noise is large!! // if( verbose ) verbfile.printf("\tError[%d] E0 %g\t",i,simplex_errors.x[i]) simplex_noise.x[i] = -1*temptr*log(r.repick()) simplex_errors.x[i] += simplex_noise.x[i] if( verbose ) verbfile.printf("N %g\tEN %g\n",simplex_noise.x[i],simplex_errors.x[i]) } ilo = simplex_errors.min_ind() // index of smallest error (incl fluct'n) ihi = simplex_errors.max_ind() ehi = simplex_errors.x[ihi] simplex_errors.x[ihi] = -1 inhi = simplex_errors.max_ind() simplex_errors.x[ihi] = ehi elo = simplex_errors.x[ilo] enhi = simplex_errors.x[inhi] if( verbose ) { verbfile.printf("Worst point ") for i = 0, simplex.ncol-1 { verbfile.printf("%g ",simplex.getrow(ihi).x[i]) } verbfile.printf("E0 %g\tN %g EN %g\n",simplex_errors.x[ihi]-simplex_noise.x[ihi], \ simplex_noise.x[ihi],simplex_errors.x[ihi]) } // // track 'uphill' steps. // // error values without random fluct'n ehi_det = simperrors_cpy.x[ihi] elo_det = simperrors_cpy.x[ilo] enhi_det = simperrors_cpy.x[inhi] // test for completion rtol = 2 * abs(ehi - elo)/( abs(ehi) + abs(elo) ) if (rtol < ftol || iter < 0) { break } // Compute vector average of all points except highest mean_point.fill(0) for i = 0, npoints-1 { if (i != ihi) mean_point.add(simplex.getrow(i)) } mean_point.div(npoints-1) iter -= 2 old_simplex = simplex.c() if (stoprun) break esave = ehi // Begin a new iteration. First extrapolate by alpha through the face of // the simplex across from the high point, i.e. reflect the simplex from the // high point e = new_step(alpha) Ci += eval_centroid() ndone += 1 if( ndone >= nchk ) { gstop = estimate_normgrad() if( gstop ) { printf("Normalized gradient shows little change. Stop annealing.\n") break } } if (e < elo) { // if lower than the lowest... N.B Press et al use <= but this seems to lead to loops // Gives a better result than the best point, so try an additional // extrapolation by a factor of beta. if (stoprun) break if (verbose) verbfile.printf("New best point. Try a further extrapolation.\n") e = new_step(beta) // ...Extrapolate in the same direction // add Ci calculation here. if( verbose ) print_simplex_info(verbfile) Ci += eval_centroid() ndone += 1 if( ndone >= nchk ) { gstop = estimate_normgrad() if( gstop ) { printf("Normalized gradient shows little change. Stop annealing.\n") break } } e_det = simplex_errors.x[ihi]-simplex_noise.x[ihi] if( e_det < ehi_det ) { m1 += 1 // increment count of successful moves if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 // increment count of unsuccessful moves if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,e_det,ehi_det,e_det-ehi_det) } delf += (e_det - ehi_det) if( verbose ) { verbfile.printf("%g\n",delf) } } } else if (e >= enhi) { // else if higher than the second-highest... // The reflected point is worse than the second-highest, so look for an // intermediate lower point, i.e. do a 1-D contraction // // Contraction is either positive or negative: cf. // Wright MH. Direct search methods: once scorned, now respectable. // In Griffiths DF and Watson GA (eds.), Numerical Analysis 1995 // (Proceedings of the 1995 Dundee Biennial Conference in Numerical // Analysis), 191-208, Addison Wesley Longman, Harlow, U.K., 1996. if (stoprun) break if (ehi < esave) { // new point is better than the worst point; // do an outside (+) contraction if (verbose) verbfile.printf("Do a one dimensional contraction (+)\n") esave = ehi e = new_step(gamma) // ...Do a 1D positive contraction\ } else { // new point is worse than the worst point in // simplex; do an inside (-) contraction if (verbose) verbfile.printf("Do a one dimensional contraction (-)\n") e = new_step(-1*gamma) // ...Do a 1D negative contraction\ } if (e >= esave) { // if the highest point is still there... if (verbose) verbfile.printf("Contract around the best point\n") for i = 0, npoints-1 { // ...contract about the lowest point if (stoprun) break if (i != ilo) { new_point = simplex.getrow(i).add(simplex.getrow(ilo)).mul(gamma) simplex.setrow(i,new_point) if( verbose ) { verbfile.printf("E>=ESAVE, before sa_efun new_point = ") new_point.printf(verbfile) } simplex_errors.x[i] = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) simplex_noise.x[i] = 0 if( simplex_errors.x[i] < simperrors_cpy.x[i] ) { m1 += 1 if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,simplex_errors.x[i],simperrors_cpy.x[i],\ simplex_errors.x[i] - simperrors_cpy.x[i]) } delf += (simplex_errors.x[i] - simperrors_cpy.x[i]) if( verbose ) { verbfile.printf("%g\n",delf) } } } } //Calculate Ci here. Ci += eval_centroid() ndone += 1 if( ndone >= nchk ) { gstop = estimate_normgrad() if( gstop ) { printf("Normalized gradient shows little change. Stop annealing.\n") break } } iter -= npoints-1 } else { //Calculate Ci here. Ci += eval_centroid() ndone += 1 if( ndone >= nchk ) { gstop = estimate_normgrad() if( gstop ) break } if( e_det < ehi_det ) { m1 += 1 // increment count of successful moves if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 // increment count of unsuccessful moves if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,e_det,ehi_det,e_det - ehi_det) } delf += (e_det - ehi_det) if( verbose ) { verbfile.printf("%g\n",delf) } } } } else { //Calculate Ci here. Ci += eval_centroid() ndone += 1 if( ndone >= nchk ) { gstop = estimate_normgrad() if( gstop ) break } if( verbose ) verbfile.printf("Improved over the worst points. Accepted. \n") if( e_det < ehi_det ) { m1 += 1 // increment count of successful moves if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 // increment count of unsuccessful moves if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,e_det,ehi_det,e_det - ehi_det) } delf += (e_det - ehi_det) if( verbose ) { verbfile.printf("%g\n",delf) } } iter += 1 } } return iter } /***************************************************************** Same as noisy_simplex above, except it calculates m1 and m2 for initial temperature estimation. *****************************************************************/ func noisy_simplex_m1m2() { local i, e, iter, esave iter = $1 m1 = m2 = delf = 0 while (1) { printf("m1 %d + m2 %d = %d < %d\n",m1,m2,m1+m2,iter_per_step) verbfile.printf("m1 %d + m2 %d = %d < %d\n",m1,m2,m1+m2,iter_per_step) if( m1+m2 > iter_per_step ) break // find the highest (worst), next highest and lowest (best) points // // whenever we "look" at a vertex, it gets a random thermal fluctuation. // simplex_errors stores the error of the simplex points, // plus these fluctuations. // simperrors_cpy = simplex_errors.c.sub(simplex_noise) simplex_errors = simperrors_cpy.c for i = 0, npoints-1 { // By storing the noise applied at each step, in addition to the // noise-amplified error, we are able to retain the actual function // values observed, which is crucial to the Press algorithm. Otherwise // we keep adding noise on top of itself, which is a real problem when the // noise is large!! // if( verbose ) { verbfile.printf("\tError[%d] E0 %g\t",i,simplex_errors.x[i]) } simplex_noise.x[i] = -1*temptr*log(r.repick()) simplex_errors.x[i] += simplex_noise.x[i] if( verbose ) { verbfile.printf("N %g\tEN %g\n",simplex_noise.x[i],simplex_errors.x[i]) } } ilo = simplex_errors.min_ind() // index of smallest error (incl fluct'n) ihi = simplex_errors.max_ind() ehi = simplex_errors.x[ihi] simplex_errors.x[ihi] = -1 inhi = simplex_errors.max_ind() simplex_errors.x[ihi] = ehi elo = simplex_errors.x[ilo] enhi = simplex_errors.x[inhi] if( verbose ) { verbfile.printf("Worst point ") for i = 0, simplex.ncol-1 { verbfile.printf("%g ",simplex.getrow(ihi).x[i]) } verbfile.printf("E0 %g\tN %g EN %g\n",simplex_errors.x[ihi]-simplex_noise.x[ihi], \ simplex_noise.x[ihi],simplex_errors.x[ihi]) } // // track 'uphill' steps. // // error values without random fluct'n ehi_det = simperrors_cpy.x[ihi] elo_det = simperrors_cpy.x[ilo] enhi_det = simperrors_cpy.x[inhi] // test for completion rtol = 2 * abs(ehi - elo)/( abs(ehi) + abs(elo) ) // Compute vector average of all points except highest mean_point.fill(0) for i = 0, npoints-1 { if (i != ihi) mean_point.add(simplex.getrow(i)) } mean_point.div(npoints-1) iter -= 2 old_simplex = simplex.c() if( m1+m2 > iter_per_step ) break esave = ehi // Begin a new iteration. First extrapolate by alpha through the face of // the simplex across from the high point, i.e. reflect the simplex from the // high point e = new_step(alpha) if (e < elo) { // if lower than the lowest... N.B Press et al use <= but this seems to lead to loops // Gives a better result than the best point, so try an additional // extrapolation by a factor of beta. if( m1+m2 > iter_per_step ) break if (verbose) verbfile.printf("New best point. Try a further extrapolation.\n") e = new_step(beta) // ...Extrapolate in the same direction if( verbose ) print_simplex_info(verbfile) e_det = simplex_errors.x[ihi]-simplex_noise.x[ihi] if( e_det < ehi_det ) { m1 += 1 // increment count of successful moves if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 // increment count of unsuccessful moves if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,e_det,ehi_det,e_det-ehi_det) } delf += (e_det - ehi_det) if( verbose ) { verbfile.printf("%g\n",delf) } } } else if (e >= enhi) { // else if higher than the second-highest... // The reflected point is worse than the second-highest, so look for an // intermediate lower point, i.e. do a 1-D contraction // // Contraction is either positive or negative: cf. // Wright MH. Direct search methods: once scorned, now respectable. // In Griffiths DF and Watson GA (eds.), Numerical Analysis 1995 // (Proceedings of the 1995 Dundee Biennial Conference in Numerical // Analysis), 191-208, Addison Wesley Longman, Harlow, U.K., 1996. if( m1+m2 > iter_per_step ) break if (ehi < esave) { // new point is better than the worst point; // do an outside (+) contraction if (verbose) verbfile.printf("Do a one dimensional contraction (+)\n") esave = ehi e = new_step(gamma) // ...Do a 1D positive contraction\ } else { // new point is worse than the worst point in // simplex; do an inside (-) contraction if (verbose) verbfile.printf("Do a one dimensional contraction (-)\n") e = new_step(-1*gamma) // ...Do a 1D negative contraction\ } if (e >= esave) { // if the highest point is still there... if (verbose) verbfile.printf("Contract around the best point\n") for i = 0, npoints-1 { // ...contract about the lowest point if( m1+m2 > iter_per_step ) break if (i != ilo) { new_point = simplex.getrow(i).add(simplex.getrow(ilo)).mul(gamma) simplex.setrow(i,new_point) if( verbose ) { verbfile.printf("M1M2: E>=ESAVE, before sa_efun new_point = ") new_point.printf(verbfile) } simplex_errors.x[i] = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) simplex_noise.x[i] = 0 if( simplex_errors.x[i] < simperrors_cpy.x[i] ) { m1 += 1 if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,simplex_errors.x[i],simperrors_cpy.x[i],\ simplex_errors.x[i] - simperrors_cpy.x[i]) } delf += (simplex_errors.x[i] - simperrors_cpy.x[i]) if( verbose ) { verbfile.printf("%g\n",delf) } } } } iter -= npoints-1 } else { if( e_det < ehi_det ) { m1 += 1 // increment count of successful moves if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 // increment count of unsuccessful moves if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,e_det,ehi_det,e_det - ehi_det) } delf += (e_det - ehi_det) if( verbose ) { verbfile.printf("%g\n",delf) } } } } else { if( verbose ) verbfile.printf("Improved over the worst points. Accepted. \n") if( e_det < ehi_det ) { m1 += 1 // increment count of successful moves if( verbose ) verbfile.printf("increment m1 %d\n",m1) } else { m2 += 1 // increment count of unsuccessful moves if( verbose ) { verbfile.printf("increment m2 %d curr_delf %g - %g = %g tot_delf ",\ m2,e_det,ehi_det,e_det - ehi_det) } delf += (e_det - ehi_det) if( verbose ) { verbfile.printf("%g\n",delf) } } iter += 1 } } if( m2 > 0 ) delf /= m2 return iter } func new_step() { local e, ttime, fluc, i new_point = mean_point.c.mul(1+$1).sub(old_simplex.getrow(ihi).mul($1)) if( out_of_bounds(new_point) ) { e = bd_pnlty if( wrapfile.isopen ) { wrapfile.printf("Point was ") new_point.printf(wrapfile) } // if the first attempt is out-of-bounds, recenter the // point around the best point of the simplex so far. if( verbose ) { verbfile.printf("Before recenter new_point = ") new_point.printf(verbfile) } recenter_point() if( verbose ) { verbfile.printf("After recenter new_point = ") new_point.printf(verbfile) } } if( verbose ) { verbfile.printf("NEW_STEP, before sa_efun new_point = ") new_point.printf(verbfile) } e = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) if( out_of_bounds(new_point) ) { // if point is still out of bounds, even after recentering, // penalize it. e += bd_pnlty } e_det = e enow = e if (e <= eb) { // Save the best-ever point if( verbose ) { verbfile.printf("Best point was = ") best_point.printf(verbfile) } best_point = new_point eb = e if( verbose ) { verbfile.printf("Best point NOW = ") best_point.printf(verbfile) } } fluc = -1*temptr*log(r.repick()) e -= fluc if( verbose ) { verbfile.printf("\tNew point proposed ") for i = 0, simplex.ncol-1 { verbfile.printf("%g ",new_point.x[i]) } verbfile.printf("E0 %g\tN %g EN %g\n",e+fluc,fluc,e) } if (e < ehi) { if (verbose) { verbfile.printf("Replaced highest point\t") if( e_det < ehi_det ) { verbfile.printf("true downhill point %g (%g) replaces %g (%g)\n",e,e_det,ehi,ehi_det) } else { verbfile.printf("an uphill point added to simplex %g (%g) replaces %g (%g)\n",e,e_det,ehi,ehi_det) } } ehi = e if( verbose ) { verbfile.printf("\tPoint was ") simplex.getrow(ihi).printf(verbfile) verbfile.printf("\tNow is ") new_point.printf(verbfile) } simplex.setrow(ihi,new_point) simplex_errors.x[ihi] = e simplex_noise.x[ihi] = -1*fluc if( verbose ) { verbfile.printf("After replacement, ") print_simplex_info(verbfile) } } return e } proc recenter_point() { local j, abval, bdval, rval, tmplo // Center about the best point, WITHOUT the noise. tmplo = simplex_errors.c.sub(simplex_noise).min_ind() if( verbose ) { verbfile.printf("Recentering out-of-bounds point ") for j = 0, new_point.size-1 { verbfile.printf(" %g",new_point.x[j]) } verbfile.printf("\nabout best point %d with error %g (%g) (K=%g)\n",tmplo,simplex_errors.x[tmplo]-simplex_noise.x[tmplo],simplex_errors.x[tmplo],Kbound) for j = 0, new_point.size-1 { verbfile.printf(" %g",simplex.getrow(tmplo).x[j]) } verbfile.printf("\n") } for j = 0, nparam-1 { if( (pf.parmlist_use.object(j).low <= -1e6) || \ (pf.parmlist_use.object(j).high >= 1e6) ) { bdval = 2*simplex.getrow(tmplo).abs.x[j] } else { bdval = Kbound*(code_high.x[j]-code_low.x[j]) } rval = 0.5-r.repick() new_point.x[j] = simplex.x[tmplo][j] + rval*bdval } if( verbose ) { verbfile.printf("\nNow using ") for j = 0, new_point.size-1 { verbfile.printf(" %g",new_point.x[j]) } verbfile.printf("\n") } } proc estimate_inittemp() { local lines minerr = -1 restart_temptr = restart_factor * start_temptr sprint(verbname,"%s.est",savepath_name) if (saveflag) { sprint(savefit_name,"%s.estfit",savepath_name) lines = 0 } verbfile.wopen(verbname) pf.doarg_get(new_point) set_coded_bounds() nefun = 0 eb = 1e9 st = startsw() time = st if (new_point.size == 0) { minerr = pf.efun(0, &time) // time is dummy here } else { if (stoprun) {return minerr} // Estimate the initial starting temp as in Cardoso et al. 1996 pf.doarg_get(new_point) minerr = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) start_temptr = 1e5*minerr printf("Initial error %g; start temp at %g\n",minerr,start_temptr) verbfile.printf("Initial error %g; start temp at %g\n",minerr,start_temptr) verbose = 0 start_temptr = temp_est() new_point = best_point.c minerr = sa_efun(new_point.size(),&new_point.x[0],out_of_bounds(new_point)) } time = startsw() - st if( savepath.isopen ) { savepath.close } best_point.printf("%9.4g") verbfile.printf("Best point is ") best_point.printf(verbfile) verbfile.close } proc temp_est() { local i, id, rval wrapfile = new File() printf("Estimating initial temperature\n") if( verbfile.isopen ) verbfile.printf("Estimating initial temperature\n") initialize_simplex() //Start the optimisation iter = -1 temptr = start_temptr //iter_per_step = pf.parmlist_use.count * 100 printf("Try starting temp of %g; Do %d iterations\n",start_temptr,iter_per_step) if( verbfile.isopen ) { verbfile.printf("Try starting temp of %g; Do %d iterations\n",start_temptr,iter_per_step) } // Only do one iteration of the outer loop, i.e. iter_per_step function iterations iter = iter_per_step iter = noisy_simplex_m1m2(iter) printf("\nSimplex complete.\n") printf("Estimating initial temperature...\nm1 = %g\tm2 = %g\tdel F = %g\n",m1,m2,delf) if( verbfile.isopen ) { verbfile.printf("\nSimplex complete.\n") verbfile.printf("Estimating initial temperature...\nm1 = %g\tm2 = %g\tdel F = %g\n",m1,m2,delf) } chitmp = 0.95 if( m2 > 0 ) { tmpval = (m2*chitmp - m1*(1-chitmp))/m2 } else { tmpval = 1e6 } tempest = -delf / log(tmpval) printf("Initial temperature estimate is %g\n",tempest) printf("Ended noisy_simplex_m1m2\n") if( verbfile.isopen ) { verbfile.printf("Initial temperature estimate is %g\n",tempest) verbfile.printf("Ended noisy_simplex_m1m2\n") } } proc fit_simanneal_seq() { local i, id, rval wrapfile = new File() initialize_simplex() //Start the optimisation iter = -1 temptr = start_temptr if( saveflag ) { sprint(rest_name, "%s.rst",savepath_name) write_restart_file() } while (iter < 0 && !stoprun && temptr > quit_temptr) { iter = iter_per_step iter = noisy_simplex(iter) if( verbose ) { verbfile.printf("Temp = %g, m1 %d m2 %d delf %g\n",temptr,m1,m2,delf) } if (stoprun) return if( iter < 0 && simplx_stop < 0 ) { simplx_stop = 1 simplx_stoptrial = nefun simplx_temptr = temptr } anneal_step(ann_sched) } if( wrapfile.isopen ) { wrapfile.close } } /***************************************************** Apply the annealing schedule *****************************************************/ proc anneal_step() { local newTemptr, i, in_simplex, oldTemptr time = startsw() - st if (saveflag) { write_restart_file() // this is the beginning of the annealing step. Write the current // best to the savepath.fit file. savepath.aopen(savefit_name) lines += 1 if( verbose ) { savepath.printf("AS\t") } savepath.printf("%g %d %d %-12.8g ", temptr, nefun, ttime, minerr) save_best_params(savepath) savepath.close // best_point doesn't give the actual *values* of the parameters being // fitted; use saveval // // This writes the CURRENT error and parameter set, at the start of // this annealing step. annealpath.aopen(annealfit_name) annealpath.printf("%g %d %d %-12.8g ", temptr, nefun, time, enow) saveval(annealpath) annealpath.close } printf("temptr=%g nefun=%d time=%d eb=%-12.8g\n", temptr, nefun, time, eb) oldTemptr = temptr if( $1 == 1 ) { // more complex schedule newTemptr = 1.0*(elo-eb) if (newTemptr < temptr) { if (newTemptr > 0.5*temptr) { temptr = newTemptr } else { temptr = 0.5*temptr } } } else { // simplest annealing schedule temptr *= cool_rate } // update Kbound, the compression parameter for bound constraints anneal_count += 1 if( anneal_count/2 == int(anneal_count/2) ) { // an even annealing step Kbound = Kbound_m2 * temptr / oldTemptr if( verbose ) { printf("Updating Kbound compression, anneal step %d\n",anneal_count) printf("Kbound was 1, now Kbound = %g * %g / %g = %g\n", Kbound_m2,temptr,oldTemptr,Kbound) verbfile.printf("Updating Kbound compression, anneal step %d\n",anneal_count) verbfile.printf("Kbound was 1, now Kbound = %g * %g / %g = %g\n", Kbound_m2,temptr,oldTemptr,Kbound) } } else { Kbound_m2 = Kbound Kbound = 1 if( verbose ) { printf("Updating Kbound compression, anneal step %d\n",anneal_count) printf("Kbound was %g, now Kbound = %g\n",Kbound_m2,Kbound) verbfile.printf("Updating Kbound compression, anneal step %d\n",anneal_count) verbfile.printf("Kbound was %g, now Kbound = %g\n",Kbound_m2,Kbound) } } if (temptr < restart_temptr) { restart_temptr *= restart_factor in_simplex = 0 for i = 0, npoints-1 { in_simplex += simplex.getrow(i).eq(best_point) } if (in_simplex > 0) { print "Best point is in simplex. No restart." } else { simplex.setrow(ihi,best_point) printf("Restarting at temperature %g\n",temptr) } } } /*** The vector "best_point" only stores the parameters used in the search. To get all parameters, you must retrieve data from pf.parmlist ***/ proc save_best_params() { local i, use_idx // $o1 is the file to write use_idx = 0 tl = pf.parmlist printf("Writing best_point = ") for i=0, tl.count-1 { if( tl.object(i).doarg ) { $o1.printf("%-12.8g ",true_value(best_point.x[use_idx],use_idx)) printf("%-12.8g ",true_value(best_point.x[use_idx],use_idx)) use_idx += 1 } else { $o1.printf("%-12.8g ", tl.object(i).val) printf("%-12.8g ", tl.object(i).val) //$o1.printf("%-12.8g ", tl.object(i).val.x[0]) //printf("%-12.8g ", tl.object(i).val.x[0]) } } $o1.printf("\n") printf("\n") } proc saveval() { local i // $o1 is the file to write if (numarg() == 2) $o1.printf("%s", $s2) tl = pf.parmlist for i=0, tl.count-1 { $o1.printf("%-12.8g ", tl.object(i).val) //$o1.printf("%-12.8g ", tl.object(i).val.x[0]) } $o1.printf("\n") } func out_of_bounds() { local i for i = 0, $o1.size-1 { rval = $o1.x[i] if( rval < code_low.x[i] || rval > code_high.x[i] ) { rval = true_value($o1.x[i],i) printf("Param %s = %g (%g) out of range [%g, %g]; adjust point\n",\ pf.parmlist_use.object(i).name,$o1.x[i],rval,\ pf.parmlist_use.object(i).low,pf.parmlist_use.object(i).high) if( verbose ) verbfile.printf("Param %s (%g) out of range [%g, %g]; adjust point\n",\ pf.parmlist_use.object(i).name,rval,\ pf.parmlist_use.object(i).low,pf.parmlist_use.object(i).high) return 1 } } return 0 } func true_value() { if (pf.parmlist_use.object($2).uselog) { if (pf.parmlist_use.object($2).low > 0) { return exp($1) }else if (pf.parmlist_use.object($2).high < 0) { return exp(-$1) } return $1 }else{ return $1 } } func code_value() { if (pf.parmlist_use.object($2).uselog) { if (pf.parmlist_use.object($2).low > 0) { return log($1) }else if (pf.parmlist_use.object($2).high < 0) { return log(-$1) } return $1 }else{ return $1 } } /********************************************************** write_point2file() writes the specified point (vector) $o1 to the file $o2. if the point is logscale it is converted back to its true value. **********************************************************/ proc write_point2file() { local i // $o2 is the file to write // $o1 is the point to write for i=0, $o1.size-1 { $o2.printf("%-12.8g ", true_value($o1.x[i],i)) } $o2.printf("\n") } proc print_simplex_info() { local i, j $o1.printf("Simplex info:\n") for i = 0, simplex.nrow-1 { $o1.printf("[%d]\t",i) for j = 0, simplex.ncol-1 { $o1.printf("%g ",simplex.getrow(i).x[j]) } $o1.printf("E0 %g\tN %g\tEN %g\n",simplex_errors.x[i]-simplex_noise.x[i],\ simplex_noise.x[i],simplex_errors.x[i]) } } func eval_centroid() { local i, cent_err centrd.fill(0) for i = 0, npoints-1 { centrd.add(simplex.getrow(i)) } centrd.div(npoints) if( verbose ) { verbfile.printf("CNTRD, before sa_efun new_point = ") centrd.printf(verbfile) } cent_err = sa_efun(centrd.size(),¢rd.x[0],1) if( verbose ) { verbfile.printf("At this step, centroid is (") for i = 0, centrd.size-1 { verbfile.printf(" %g",centrd.x[i]) } verbfile.printf(") error %g\n",cent_err) } return cent_err } proc print_info_file() { local i, col if( savepath.isopen ) savepath.close sprint(savefit_name,"%s.info",savepath_name) savepath.wopen(savefit_name) savepath.printf("Sim Annealing, improved Bounded (Cardoso algorithm)\n") savepath.printf("Initial temp was %g\n",start_temptr) savepath.printf("Cooling rate was %g\n",cool_rate) savepath.printf("Iterations per cooling step %d\n",iter_per_step) savepath.printf("ftol = %g\n",ftol) savepath.printf("gtol = %g\n",gtol) savepath.printf("Simplex collapsed after %d function evaluations, temp %g\n",simplx_stoptrial,simplx_temptr) col = 1 savepath.printf("Columns of file %s.fit are as follows: \n",savepath_name) savepath.printf("[%d]\tCurrent temperature\n",col) col += 1 savepath.printf("[%d]\tFunction evaluation count\n",col) col += 1 savepath.printf("[%d]\tRun time\n",col) col += 1 savepath.printf("[%d]\tMin Error\n",col) col += 1 savepath.printf("\nThe remaining columns give the values of the following parameters:\n") for i = 0, pf.parmlist.count-1 { savepath.printf("[%d]\t%s\n",col,pf.parmlist.object(i).name) col += 1 } savepath.printf("\nThe following %d parameters were optimized:\n",pf.parmlist_use.count) for i = 0, pf.parmlist_use.count-1 { savepath.printf("[%d]\t%s\n",i,pf.parmlist_use.object(i).name) } savepath.close } func estimate_normgrad() { local gval, gsmall //we've just passed a multiple of 5XN; recompute Ci & Ci-1 CiM1_avg = Ci_avg Ci_avg = Ci / Ni gval = (Ci_avg-CiM1_avg)/(Ci_avg*Ni) if( gval < gtol ) { gsmall = 1 } else { gsmall = 0 } printf("\tNormalized gradient %g < %g\n",gval,gtol) if( verbose ) { verbfile.printf("...Testing normalized gradient: ") verbfile.printf("Iteration %d, multiple of %d\n",ndone,Ni) verbfile.printf("Ci = %g, CiM1 = %g, grad ~ %g...",\ Ci_avg,CiM1_avg,(Ci_avg-CiM1_avg)/(Ci_avg*Ni)) if( !gsmall ) verbfile.printf("Gradient test failed (%g)",ftol) verbfile.printf("\n") } nchk += Ni return 0 // at the moment, we want to stop only when temperature is small enough. //return gsmall } proc write_restart_file() { local i, j, use_idx // write current simplex to restart file printf("Writing output file %s\n",rest_name) tl = pf.parmlist rest_file.wopen(rest_name) rest_file.printf("%g %d %d\n",temptr,tl.count,simplex.nrow) for i = 0, tl.count-1 { if( tl.object(i).doarg ) { rest_file.printf("1 ") } else { rest_file.printf("0 ") } } rest_file.printf("\n") for j = 0, simplex.nrow-1 { use_idx = 0 for i=0, tl.count-1 { if( tl.object(i).doarg ) { rest_file.printf("%-12.8g ",true_value(simplex.getrow(j).x[use_idx],use_idx)) use_idx += 1 } else { rest_file.printf("%-12.8g ", tl.object(i).val) } } rest_file.printf("\n") } rest_file.close } proc get_rstname() { string_dialog("Enter restart filename",$s1) } /************************************************************ set_coded_bounds() Often throughout the search we need to check how a point compares to its bounds. The user may have specified that a log transform of the data is desired, complicating this comparison. Rather than (possibly) applying a log transform to the bounds each time such a check is done, set up the transformed bounds up front. ************************************************************/ proc set_coded_bounds() { local i code_low = new Vector() code_high = new Vector() for i = 0, pf.parmlist_use.count-1 { code_low.append( code_value(pf.parmlist_use.object(i).low, i)) code_high.append(code_value(pf.parmlist_use.object(i).high,i)) } if( verbose ) { verbfile.printf("Lower bounds were : ") for i = 0, pf.parmlist_use.count-1 { verbfile.printf("%g ",pf.parmlist_use.object(i).low) } verbfile.printf("\nTransformed to : ") code_low.printf(verbfile) verbfile.printf("Upper bounds were : ") for i = 0, pf.parmlist_use.count-1 { verbfile.printf("%g ",pf.parmlist_use.object(i).high) } verbfile.printf("\nTransformed to : ") code_high.printf(verbfile) } } endtemplate MulfitSA_Rctr