/***************************************************************************** Major portions of this software are copyrighted by the Medical College of Wisconsin, 1994-2000, and are released under the Gnu General Public License, Version 2. See the file README.Copyright for details. ******************************************************************************/ /*---------------------------------------------------------------------------*/ /* This file contains routines for implementing the Simplex algorithm for non-linear optimization to estimate the unknown parameters. The Simplex algorithm is adapted from: A Jump Start Course in C++ Programming File: Simplex.c Author: B. Douglas Ward Date: 28 January 2000 Note: The calling program should include the following statements prior to "#include "Simplex.c" #define DIMENSION p where p = number of parameters to be estimated #include "randgen.c" float calc_error (float * vertex); */ /*---------------------------------------------------------------------------*/ /* Global variables and constants. */ int count = 0; /* count of error evaluations */ int number_restarts = 0; /* count of simplex algorithm restarts */ /*---------------------------------------------------------------------------*/ void allocate_arrays (float *** simplex, float ** centroid, float ** response, float ** step_size, float ** test1, float ** test2) { int i; *centroid = (float *) malloc (sizeof(float) * DIMENSION); *response = (float *) malloc (sizeof(float) * (DIMENSION+1)); *step_size = (float *) malloc (sizeof(float) * DIMENSION); *test1 = (float *) malloc (sizeof(float) * DIMENSION); *test2 = (float *) malloc (sizeof(float) * DIMENSION); *simplex = (float **) malloc (sizeof(float *) * (DIMENSION+1)); for (i = 0; i < DIMENSION+1; i++) (*simplex)[i] = (float *) malloc (sizeof(float) * DIMENSION); } /*---------------------------------------------------------------------------*/ void deallocate_arrays (float *** simplex, float ** centroid, float ** response, float ** step_size, float ** test1, float ** test2) { int i; free (*centroid); *centroid = NULL; free (*response); *response = NULL; free (*step_size); *step_size = NULL; free (*test1); *test1 = NULL; free (*test2); *test2 = NULL; for (i = 0; i < DIMENSION+1; i++) { free ((*simplex)[i]); (*simplex)[i] = NULL; } free (*simplex); *simplex = NULL; } /*---------------------------------------------------------------------------*/ void eval_vertices (float * response, int * worst, int * next, int * best) { int i; /* initialize values */ *worst = 0; *best = 0; /* find the best and worst */ for (i = 1; i < DIMENSION+1; i++) { if (response[i] > response[*worst]) *worst = i; if (response[i] < response[*best]) *best = i; } /* find the next worst index */ if (*worst == 0) *next = 1; else *next = 0; for (i = 0; i < DIMENSION+1; i++) if ((i != *worst) && (response[i] > response[*next])) *next = i; } /*---------------------------------------------------------------------------*/ void restart (float ** simplex, float * response, float * step_size) { const float STEP_FACTOR = 0.9; int i, j; int worst, next, best; float minval, maxval; /* find the current best vertex */ eval_vertices (response, &worst, &next, &best); /* set the first vertex to the current best */ for (i = 0; i < DIMENSION; i++) simplex[0][i] = simplex[best][i]; /* decrease step size */ for (i = 0; i < DIMENSION; i++) step_size[i] *= STEP_FACTOR; /* set up remaining vertices of simplex using new step size */ for (i = 1; i < DIMENSION+1; i++) for (j = 0; j < DIMENSION; j++) { minval = simplex[0][j] - step_size[j]; maxval = simplex[0][j] + step_size[j]; simplex[i][j] = rand_uniform (minval, maxval); } /* initialize response for each vector */ for (i = 0; i < DIMENSION+1; i++) response[i] = calc_error (simplex[i]); } /*---------------------------------------------------------------------------*/ /* Calculate the centroid of the simplex, ignoring the worst vertex. */ void calc_centroid (float ** simplex, int worst, float * centroid) { int i, j; for (i = 0; i < DIMENSION; i++) { centroid[i] = 0.0; /* add each vertex, except the worst */ for (j = 0; j < DIMENSION+1; j++) if (j != worst) centroid[i] += simplex[j][i]; } /* divide by the number of vertices */ for (i = 0; i < DIMENSION; i++) centroid[i] /= DIMENSION; } /*---------------------------------------------------------------------------*/ /* Calculate the reflection of the worst vertex about the centroid. */ void calc_reflection (float ** simplex, float * centroid, int worst, float coef, float * vertex) { int i; for (i = 0; i < DIMENSION; i++) vertex[i] = centroid[i] + coef*(centroid[i] - simplex[worst][i]); } /*---------------------------------------------------------------------------*/ /* Replace a vertex of the simplex. */ void replace (float ** simplex, float * response, int index, float * vertex, float resp) { int i; for (i = 0; i < DIMENSION; i++) simplex[index][i] = vertex[i]; response[index] = resp; } /*---------------------------------------------------------------------------*/ /* Calculate goodness of fit. */ float calc_good_fit (float * response) { int i; float avg, sd, tmp; /* average the response values */ avg = 0.0; for (i = 0; i < DIMENSION+1; i++) avg += response[i]; avg /= DIMENSION+1; /* compute standard deviation of response */ sd = 0.0; for (i = 0; i < DIMENSION+1; i++) { tmp = response[i] - avg; sd += tmp*tmp; } sd /= DIMENSION; return (sqrt(sd)); } /*---------------------------------------------------------------------------*/ /* Perform initialization for the Simplex algorithm. */ void simplex_initialize (float * parameters, float ** simplex, float * response, float * step_size) { int i, j; int worst, next, best; float resp; float minval, maxval; for (j = 0; j < DIMENSION; j++) { simplex[0][j] = parameters[j]; step_size[j] = 0.5 * parameters[j]; } for (i = 1; i < DIMENSION+1; i++) for (j = 0; j < DIMENSION; j++) { minval = simplex[0][j] - step_size[j]; maxval = simplex[0][j] + step_size[j]; simplex[i][j] = rand_uniform (minval, maxval); } for (i = 0; i < DIMENSION+1; i++) response[i] = calc_error (simplex[i]); for (i = 1; i < 500; i++) { for (j = 0; j < DIMENSION; j++) { minval = simplex[0][j] - step_size[j]; maxval = simplex[0][j] + step_size[j]; parameters[j] = rand_uniform (minval, maxval); } resp = calc_error (parameters); eval_vertices (response, &worst, &next, &best); if (resp < response[worst]) replace (simplex, response, worst, parameters, resp); } } /*---------------------------------------------------------------------------*/ /* Use Simplex algorithm to estimate the voxel intensity distribution. The Simplex algorithm is adapted from: A Jump Start Course in C++ Programming */ void simplex_optimization (float * parameters, float * sse) { const int MAX_ITERATIONS = 100; const int MAX_RESTARTS = 25; const float EXPANSION_COEF = 2.0; const float REFLECTION_COEF = 1.0; const float CONTRACTION_COEF = 0.5; const float TOLERANCE = 1.0e-10; float ** simplex = NULL; float * centroid = NULL; float * response = NULL; float * step_size = NULL; float * test1 = NULL; float * test2 = NULL; float resp1, resp2; int i, worst, best, next; int num_iter, num_restarts; int done; float fit; allocate_arrays (&simplex, ¢roid, &response, &step_size, &test1, &test2); simplex_initialize (parameters, simplex, response, step_size); /* 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 (response, &worst, &next, &best); calc_centroid (simplex, worst, centroid); /* reflect the worst point through the centroid */ calc_reflection (simplex, centroid, worst, REFLECTION_COEF, test1); resp1 = calc_error (test1); /* 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 (simplex, centroid, worst, EXPANSION_COEF, test2); resp2 = calc_error (test2); if (resp2 <= resp1) /* keep expansion */ replace (simplex, response, worst, test2, resp2); else /* keep reflection */ replace (simplex, response, worst, test1, resp1); } else if (resp1 < response[next]) { /* new response is between the best and next worst so keep reflection */ replace (simplex, response, worst, test1, resp1); } else { /* try contraction */ if (resp1 >= response[worst]) calc_reflection (simplex, centroid, worst, -CONTRACTION_COEF, test2); else calc_reflection (simplex, centroid, worst, CONTRACTION_COEF, test2); resp2 = calc_error (test2); /* 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 (simplex, response, step_size); } else /* keep contraction */ replace (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 (simplex, response, step_size); } /* 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 (response); if (fit <= TOLERANCE) done = 1; /* if done, copy the best solution to the output array */ if (done) { eval_vertices (response, &worst, &next, &best); for (i = 0; i < DIMENSION; i++) parameters[i] = simplex[best][i]; *sse = response[best]; } } /* while (!done) */ number_restarts = num_restarts; deallocate_arrays (&simplex, ¢roid, &response, &step_size, &test1, &test2); } /*---------------------------------------------------------------------------*/