void simplex_optimization ( vfp nmodel, /* pointer to noise model */ vfp smodel, /* pointer to signal model */ int r, /* number of parameters in the noise model */ int p, /* number of parameters in the signal model */ float * min_nconstr, /* minimum parameter constraints for noise model */ float * max_nconstr, /* maximum parameter constraints for noise model */ float * min_sconstr, /* minimum parameter constraints for signal model */ float * max_sconstr, /* maximum parameter constraints for signal model */ int nabs, /* use absolute constraints for noise parameters */ int ts_length, /* length of time series array */ float ** x_array, /* independent variable matrix */ float * ts_array, /* observed time series */ float * par_rdcd, /* estimated parameters for the reduced model */ float * parameters, /* estimated parameters */ float * sse /* error sum of squares */ ) { const int MAX_ITERATIONS = 50; /* maximum number of iterations */ const int MAX_RESTARTS = 5; /* maximum number of restarts */ const float EXPANSION_COEF = 2.0; /* expansion coefficient */ const float REFLECTION_COEF = 1.0; /* reflection coefficient */ const float CONTRACTION_COEF = 0.5; /* contraction coefficient */ const float TOLERANCE = 1.0e-4; /* solution convergence tolerance */ float ** simplex = NULL; /* the simplex itself */ float * centroid = NULL; /* center of mass of the simplex */ float * response = NULL; /* error sum of squares at each vertex */ float * step_size = NULL; /* controls random placement of new vertex */ float * test1 = NULL; /* test vertex */ float * test2 = NULL; /* test vertex */ float resp1, resp2; /* error sum of squares for test vertex */ int i; /* vertex index */ int worst; /* index of worst vertex in simplex */ int best; /* index of best vertex in simplex */ int next; /* index of next-to-worst vertex in simplex */ int num_iter; /* number of simplex algorithm iterations */ int num_restarts; /* number of restarts of simplex algorithm */ int done; /* boolean for search finished */ float fit; /* array of fitted time series values */ int dimension; /* dimension of parameter space */ /*----- dimension of parameter space -----*/ dimension = r + p; /*----- allocate memory -----*/ allocate_arrays (dimension, &simplex, ¢roid, &response, &step_size, &test1, &test2); /*----- initialization for simplex algorithm -----*/ initialize_simplex (dimension, nmodel, smodel, r, p, nabs, min_nconstr, max_nconstr, min_sconstr, max_sconstr, par_rdcd, parameters, simplex, response, step_size, ts_length, x_array, ts_array); /* start loop to do simplex optimization */ num_iter = 0; num_restarts = 0; done = 0; while (!done) { /*----- find the worst vertex and compute centroid of remaining simplex, discarding the worst vertex -----*/ eval_vertices (dimension, response, &worst, &next, &best); calc_centroid (dimension, simplex, worst, centroid); /*----- reflect the worst point through the centroid -----*/ calc_reflection (dimension, simplex, centroid, worst, REFLECTION_COEF, test1); resp1 = calc_sse (nmodel, smodel, r, p, nabs, min_nconstr, max_nconstr, min_sconstr, max_sconstr, par_rdcd, test1, ts_length, x_array, ts_array); /*----- test the reflection against the best vertex and expand it if the reflection is better. if not, keep the reflection -----*/ if (resp1 < response[best]) { /*----- try expanding -----*/ calc_reflection (dimension, simplex, centroid, worst, EXPANSION_COEF, test2); resp2 = calc_sse (nmodel, smodel, r, p, nabs, min_nconstr, max_nconstr, min_sconstr, max_sconstr, par_rdcd, test2, ts_length, x_array, ts_array); if (resp2 <= resp1) /* keep expansion */ replace (dimension, simplex, response, worst, test2, resp2); else /* keep reflection */ replace (dimension, simplex, response, worst, test1, resp1); } else if (resp1 < response[next]) { /*----- new response is between the best and next worst so keep reflection -----*/ replace (dimension, simplex, response, worst, test1, resp1); } else { /*----- try contraction -----*/ if (resp1 >= response[worst]) calc_reflection (dimension, simplex, centroid, worst, -CONTRACTION_COEF, test2); else calc_reflection (dimension, simplex, centroid, worst, CONTRACTION_COEF, test2); resp2 = calc_sse (nmodel, smodel, r, p, nabs, min_nconstr, max_nconstr, min_sconstr, max_sconstr, par_rdcd, test2, ts_length, x_array, ts_array); /*---- test the contracted response against the worst response ----*/ if (resp2 > response[worst]) { /*----- new contracted response is worse, so decrease step size and restart -----*/ num_iter = 0; num_restarts += 1; restart (dimension, nmodel, smodel, r, p, nabs, min_nconstr, max_nconstr, min_sconstr, max_sconstr, par_rdcd, simplex, response, step_size, ts_length, x_array, ts_array); } else /*----- keep contraction -----*/ replace (dimension, simplex, response, worst, test2, resp2); } /*----- test to determine when to stop. first, check the number of iterations -----*/ num_iter += 1; /*----- increment iteration counter -----*/ if (num_iter >= MAX_ITERATIONS) { /*----- restart with smaller steps -----*/ num_iter = 0; num_restarts += 1; restart (dimension, nmodel, smodel, r, p, nabs, min_nconstr, max_nconstr, min_sconstr, max_sconstr, par_rdcd, simplex, response, step_size, ts_length, x_array, ts_array); } /*----- limit the number of restarts -----*/ if (num_restarts == MAX_RESTARTS) done = 1; /*----- compare relative standard deviation of vertex responses against a defined tolerance limit -----*/ fit = calc_good_fit (dimension, response); if (fit <= TOLERANCE) done = 1; /*----- if done, copy the best solution to the output array -----*/ if (done) { eval_vertices (dimension, response, &worst, &next, &best); for (i = 0; i < dimension; i++) parameters[i] = simplex[best][i]; *sse = response[best]; } } /*----- while (!done) -----*/ deallocate_arrays (dimension, &simplex, ¢roid, &response, &step_size, &test1, &test2); }
void NelderMeadSolver::solve(const Function& function, SolverResults* results) const { double global_start_time = wall_time(); // Dimension of problem. size_t n = function.get_number_of_scalars(); if (n == 0) { results->exit_condition = SolverResults::FUNCTION_TOLERANCE; return; } // The Nelder-Mead simplex. std::vector<SimplexPoint> simplex(n + 1); // Copy the user state to the current point. Eigen::VectorXd x; function.copy_user_to_global(&x); initialize_simplex(function, x, &simplex); SimplexPoint mean_point; SimplexPoint reflection_point; SimplexPoint expansion_point; mean_point.x.resize(n); reflection_point.x.resize(n); expansion_point.x.resize(n); double fmin = std::numeric_limits<double>::quiet_NaN(); double fmax = std::numeric_limits<double>::quiet_NaN(); double fval = std::numeric_limits<double>::quiet_NaN(); double area = std::numeric_limits<double>::quiet_NaN(); double area0 = std::numeric_limits<double>::quiet_NaN(); double length = std::numeric_limits<double>::quiet_NaN(); double length0 = std::numeric_limits<double>::quiet_NaN(); Eigen::MatrixXd area_mat(n, n); // // START MAIN ITERATION // results->startup_time += wall_time() - global_start_time; results->exit_condition = SolverResults::INTERNAL_ERROR; int iter = 0; int n_shrink_in_a_row = 0; while (true) { // // In each iteration, the worst point in the simplex // is replaced with a new one. // double start_time = wall_time(); mean_point.x.setZero(); fval = 0; // Compute the mean of the best n points. for (size_t i = 0; i < n; ++i) { mean_point.x += simplex[i].x; fval += simplex[i].value; } fval /= double(n); mean_point.x /= double(n); fmin = simplex[0].value; fmax = simplex[n].value; const char* iteration_type = "n/a"; // Compute the reflexion point and evaluate it. reflection_point.x = 2.0 * mean_point.x - simplex[n].x; reflection_point.value = function.evaluate(reflection_point.x); bool is_shrink = false; if (simplex[0].value <= reflection_point.value && reflection_point.value < simplex[n - 1].value) { // Reflected point is neither better nor worst in the // new simplex. std::swap(reflection_point, simplex[n]); iteration_type = "Reflect 1"; } else if (reflection_point.value < simplex[0].value) { // Reflected point is better than the current best; try // to go farther along this direction. // Compute expansion point. expansion_point.x = 3.0 * mean_point.x - 2.0 * simplex[n].x; expansion_point.value = function.evaluate(expansion_point.x); if (expansion_point.value < reflection_point.value) { std::swap(expansion_point, simplex[n]); iteration_type = "Expansion"; } else { std::swap(reflection_point, simplex[n]); iteration_type = "Reflect 2"; } } else { // Reflected point is still worse than x[n]; contract. bool success = false; if (simplex[n - 1].value <= reflection_point.value && reflection_point.value < simplex[n].value) { // Try to perform "outside" contraction. expansion_point.x = 1.5 * mean_point.x - 0.5 * simplex[n].x; expansion_point.value = function.evaluate(expansion_point.x); if (expansion_point.value <= reflection_point.value) { std::swap(expansion_point, simplex[n]); success = true; iteration_type = "Outside contraction"; } } else { // Try to perform "inside" contraction. expansion_point.x = 0.5 * mean_point.x + 0.5 * simplex[n].x; expansion_point.value = function.evaluate(expansion_point.x); if (expansion_point.value < simplex[n].value) { std::swap(expansion_point, simplex[n]); success = true; iteration_type = "Inside contraction"; } } if (! success) { // Neither outside nor inside contraction was acceptable; // shrink the simplex toward the best point. for (size_t i = 1; i < n + 1; ++i) { simplex[i].x = 0.5 * (simplex[0].x + simplex[i].x); simplex[i].value = function.evaluate(simplex[i].x); iteration_type = "Shrink"; is_shrink = true; } } } std::sort(simplex.begin(), simplex.end()); results->function_evaluation_time += wall_time() - start_time; // // Test stopping criteriea // start_time = wall_time(); // Compute the area of the simplex. length = 0; for (size_t i = 0; i < n; ++i) { area_mat.col(i) = simplex[i].x - simplex[n].x; length = std::max(length, area_mat.col(i).norm()); } area = std::abs(area_mat.determinant()); if (iter == 0) { area0 = area; length0 = length; } if (area / area0 < this->area_tolerance) { results->exit_condition = SolverResults::GRADIENT_TOLERANCE; break; } if (area == 0) { results->exit_condition = SolverResults::GRADIENT_TOLERANCE; break; } if (length / length0 < this->length_tolerance) { results->exit_condition = SolverResults::GRADIENT_TOLERANCE; break; } if (is_shrink) { n_shrink_in_a_row++; } else { n_shrink_in_a_row = 0; } if (n_shrink_in_a_row > 50) { results->exit_condition = SolverResults::GRADIENT_TOLERANCE; break; } if (iter >= this->maximum_iterations) { results->exit_condition = SolverResults::NO_CONVERGENCE; break; } if (this->callback_function) { CallbackInformation information; information.objective_value = simplex[0].value; information.x = &simplex[0].x; if (!callback_function(information)) { results->exit_condition = SolverResults::USER_ABORT; break; } } results->stopping_criteria_time += wall_time() - start_time; // // Restarting // //if (area / area1 < 1e-10) { // x = simplex[0].x; // initialize_simplex(function, x, &simplex); // area1 = area; // if (this->log_function) { // this->log_function("Restarted."); // } //} // // Log the results of this iteration. // start_time = wall_time(); int log_interval = 1; if (iter > 30) { log_interval = 10; } if (iter > 200) { log_interval = 100; } if (iter > 2000) { log_interval = 1000; } if (this->log_function && iter % log_interval == 0) { char str[1024]; if (iter == 0) { this->log_function("Itr min(f) avg(f) max(f) area length type"); } std::sprintf(str, "%6d %+.3e %+.3e %+.3e %.3e %.3e %s", iter, fmin, fval, fmax, area, length, iteration_type); this->log_function(str); } results->log_time += wall_time() - start_time; iter++; } // Return the best point as solution. function.copy_global_to_user(simplex[0].x); results->total_time += wall_time() - global_start_time; if (this->log_function) { char str[1024]; std::sprintf(str, " end %+.3e %.3e %.3e", fval, area, length); this->log_function(str); } }
/***** Main Driver Function ****************************/ int main (void) { struct simplex *simpx; int best; int nfunc; double answer[5] = {-3.0, 0.0}; int ndims = 2; int npts = 3; int i, success = 0; /** Check 2D Polynomial ***************************************/ double *pts = malloc ( sizeof(double) * ndims*npts ); /* Freed in destroy_simplex */ /* Set initial Points */ pts[0] = -3.9; pts[1] = 0.1; pts[2] = 3.5; pts[3] = 1.0; pts[4] = 5.0; pts[5] = -9.0; /* Set Soft Boundaries */ double *bounds = malloc ( sizeof(double) * 3*ndims ); /* Freed in destroy_simplex */ bounds[0] = 3; bounds[1] = 3; bounds[2] = -10; bounds[3] = -10; bounds[4] = 10; bounds[5] = 10; nfunc = 0; simpx = initialize_simplex(2, 3, 2, pts, bounds, poly_2d); assert( simpx ); best = amoeba_omp (simpx, 1.0e-6, poly_2d, &nfunc, 0); printf("\n\n\n"); printf("Poly_2d: Best Point: "); for ( i = 0; i < simpx->ndims; i++) { printf("%.3f ", simpx->points[best*simpx->ndims + i]); if( fabs( answer[i] - simpx->points[best*simpx->ndims + i] ) < .01 ) { success++; } } printf("\n\tMinimum at Best Point: %.3f ", simpx->vals[best]); printf("\n\tFunction Calls: %d\n\t", nfunc); success == simpx->ndims ? printf("SUCCEEDED\n\n") : printf("FAILED\n\n"); destroy_simplex( simpx ); /********************************************************************/ /** Check 2D Polynomial with minimum of 0. ***************************/ /** Verifies no divide by 0 error. ***********************************/ nfunc = 0; double *bound = malloc ( sizeof(double) * 3*ndims ); /* Freed in destroy_simplex */ bound[0] = 3; bound[1] = 3; bound[2] = -10; bound[3] = -10; bound[4] = 10; bound[5] = 10; simpx = initialize_simplex(2, 3, 1, NULL, bound, poly_2d_0); best = amoeba_omp (simpx, 1.0e-6, poly_2d_0, &nfunc, 0); success = 0; printf("Poly_2d_0: Best Point: "); for ( i = 0; i < simpx->ndims; i++) { printf("%.3f ", simpx->points[best*simpx->ndims + i]); if( fabs( answer[i] - simpx->points[best*simpx->ndims + i] ) < .01 ) { success++; } } printf("\n\tMinimum at Best Point: %.3f ", simpx->vals[best]); printf("\n\tFunction Calls: %d\n\t", nfunc); success == simpx->ndims ? printf("SUCCEEDED\n\n") : printf("FAILED\n\n"); destroy_simplex( simpx ); /********************************************************************/ return 0; }