SDDSlib
Loading...
Searching...
No Matches
powellMin.c
Go to the documentation of this file.
1/**
2 * @file powellMin.c
3 * @brief Implementation of Powell's optimization algorithm for multidimensional minimization.
4 *
5 * @copyright
6 * - (c) 2002 The University of Chicago, as Operator of Argonne National Laboratory.
7 * - (c) 2002 The Regents of the University of California, as Operator of Los Alamos National Laboratory.
8 *
9 * @license
10 * This file is distributed under the terms of the Software License Agreement
11 * found in the file LICENSE included with this distribution.
12 *
13 * @author M. Borland, C. Saunders, R. Soliday
14 */
15
16#include "mdb.h"
17
18#define DEFAULT_MAXEVALS 100
19#define DEFAULT_MAXPASSES 5
20
21#define DEBUG 0
22
23long powellMoveToMin(double *yReturn, double *x, double *xWork, double *dx,
24 double *xLower, double *xUpper, long dims, long linMinIterations, long maxGoodSteps,
25 double (*func)(double *x, long *invalid)) {
26 double y0, y1 = 0;
27 long i, j, invalid, goodSteps, goodSteps0;
28 long divisor, division, divisions, success, evals, limitHit;
29
30 y0 = *yReturn;
31
32 /* first, try to find a direction and step-size that leads to a decrease in the function value */
33 divisor = 10;
34 success = evals = 0;
35 divisions = 20;
36 division = 0;
37 memcpy(xWork, x, sizeof(*x) * dims);
38 while (division++ < divisions) {
39 /* step in direction dx, looking for reduction */
40 for (i = 0; i < dims; i++) {
41 x[i] += dx[i] / divisor;
42 if (xLower && xLower[i] > x[i])
43 x[i] = xLower[i];
44 if (xUpper && xUpper[i] < x[i])
45 x[i] = xUpper[i];
46 }
47 y1 = (*func)(x, &invalid);
48#if DEBUG
49 fprintf(stderr, ">> values are: ");
50 for (i = 0; i < dims; i++)
51 fprintf(stderr, "%g, ", x[i]);
52 fprintf(stderr, "\n");
53 fprintf(stderr, "result is %g, invalid=%ld\n",
54 y1, invalid);
55#endif
56 evals++;
57 if (invalid)
58 y1 = 1e9 * fabs(y0) + 1;
59 if (y1 >= y0) {
60#if DEBUG
61 fprintf(stderr, ">> not improved\n");
62#endif
63 memcpy(x, xWork, sizeof(*x) * dims);
64 if (division % 2)
65 divisor *= -1;
66 else
67 divisor *= -10;
68 } else {
69#if DEBUG
70 fprintf(stderr, ">> improved\n");
71#endif
72 success = 1;
73 break;
74 }
75 }
76 if (!success) {
77#if DEBUG
78 fprintf(stderr, ">> optimization failed\n");
79#endif
80 return evals;
81 }
82#if DEBUG
83 fprintf(stderr, ">> optimization succeeded\n");
84#endif
85
86 /* continue in this direction, if possible */
87 limitHit = 0;
88 for (j = 0; !limitHit && j < linMinIterations; j++) {
89#if DEBUG
90 fprintf(stderr, ">> trying to step again\n");
91#endif
92 success = 0;
93 memcpy(xWork, x, sizeof(*x) * dims);
94 goodSteps = goodSteps0 = 0;
95 while (1) {
96 y0 = y1;
97 for (i = 0; i < dims; i++) {
98 x[i] += dx[i] / divisor;
99 if (xLower && xLower[i] > x[i]) {
100 limitHit = 1;
101 x[i] = xLower[i];
102 }
103 if (xUpper && xUpper[i] < x[i]) {
104 limitHit = 1;
105 x[i] = xUpper[i];
106 }
107 }
108 y1 = (*func)(x, &invalid);
109 evals++;
110 if (invalid)
111 y1 = 1e9 * fabs(y0) + 1;
112 if (y1 >= y0) {
113 memcpy(x, xWork, sizeof(*x) * dims);
114 y1 = y0;
115 break;
116 } else {
117 memcpy(xWork, x, sizeof(*x) * dims);
118 success = 1;
119 goodSteps++;
120 if (maxGoodSteps > 0 && goodSteps > maxGoodSteps) {
121 *yReturn = y0;
122 return evals;
123 }
124 if ((goodSteps - goodSteps0) > 5) {
125 divisor /= 2;
126 goodSteps0 = goodSteps;
127 }
128 }
129 }
130 if (success)
131 /* turn around and repeat */
132 divisor *= -PI;
133 else
134 break;
135 }
136
137 *yReturn = y0;
138 return evals;
139}
140
141long powellMinStep(double *yReturn, double *xReturn, double **dirVector, double **P,
142 double *xLower, double *xUpper, long dims, double target,
143 long linMinIterations, long limitGoodSteps,
144 double (*func)(double *x, long *invalid)) {
145 long i, invalid, evals;
146 double *yValue, yExtrap, *PExtrap = NULL, dyMax, dy;
147
148 if (!(yValue = malloc(sizeof(*yValue) * (dims + 1))) ||
149 !(PExtrap = malloc(sizeof(*PExtrap) * dims)))
150 bomb("memory allocation failure (powellMinStep)", NULL);
151
152 memcpy(P[0], xReturn, sizeof(*xReturn) * dims);
153 yValue[0] = *yReturn;
154 dyMax = -DBL_MAX;
155 evals = 0;
156 for (i = 1; i <= dims; i++) {
157#if DEBUG
158 fprintf(stderr, ">> Scanning %ld\n", i - 1);
159#endif
160 memcpy(P[i], P[i - 1], sizeof(**P) * dims);
161 yValue[i] = yValue[i - 1];
162 evals += powellMoveToMin(yValue + i, P[i], PExtrap, dirVector[i - 1], xLower, xUpper, dims,
163 linMinIterations, limitGoodSteps ? 3 : 0, func);
164 if ((dy = yValue[i] - yValue[i - 1]) > dyMax)
165 dyMax = dy;
166 }
167 if (dyMax == -DBL_MAX)
168 return evals;
169
170 for (i = 0; i < dims; i++)
171 PExtrap[i] = 2 * P[dims][i] - P[0][i];
172 yExtrap = (*func)(PExtrap, &invalid);
173 if (invalid)
174 yExtrap = 1e9 * yValue[0];
175 if (!(yExtrap >= yValue[0] ||
176 2 * (yValue[0] - 2 * yValue[dims] + yExtrap) *
177 sqr(yValue[0] - yValue[dims] - dyMax) >=
178 sqr(yValue[0] - yExtrap) * dyMax)) {
179 /* exchange dir vectors */
180 for (i = 0; i < dims - 1; i++)
181 memcpy(dirVector[i], dirVector[i + 1], sizeof(**dirVector) * dims);
182 for (i = 0; i < dims; i++)
183 dirVector[dims - 1][i] = P[dims][i] - P[0][i];
184 }
185 memcpy(xReturn, P[dims], sizeof(*xReturn) * dims);
186 *yReturn = yValue[dims];
187 free(yValue);
188 free(PExtrap);
189 return evals;
190}
191
192/**
193 * @brief Minimizes an objective function using Powell's method.
194 *
195 * This is the main function implementing Powell's optimization algorithm. It iteratively
196 * searches for the minimum of the provided objective function within the specified bounds
197 * and tolerance levels.
198 *
199 * @param yReturn Pointer to store the final function value at the minimum.
200 * @param xGuess Initial guess for the position in the parameter space.
201 * @param dxGuess Initial guess for the step sizes in each dimension.
202 * @param xLowerLimit Array of lower bounds for each dimension.
203 * @param xUpperLimit Array of upper bounds for each dimension.
204 * @param dims Number of dimensions in the parameter space.
205 * @param target Target function value; the algorithm will terminate if any value is
206 * less than or equal to this target.
207 * @param tolerance Tolerance for convergence. If negative, interpreted as a fractional
208 * tolerance; if positive, as an absolute tolerance.
209 * @param func Pointer to the objective function to be minimized. It should accept
210 * the current position and a pointer to a long indicating if the
211 * evaluation is invalid.
212 * @param report Pointer to a reporting function that is called after each pass. It
213 * receives the current minimum value, position, pass number, total
214 * evaluations, and number of dimensions.
215 * @param maxPasses Maximum number of passes (iterations) to perform.
216 * @param maxEvaluations Maximum number of function evaluations allowed.
217 * @param linMinIterations Number of linear minimization iterations to perform.
218 * @return The total number of function evaluations performed during the minimization.
219 */
221 double *yReturn,
222 double *xGuess,
223 double *dxGuess,
224 double *xLowerLimit,
225 double *xUpperLimit,
226 long dims,
227 double target, /* will return if any value is <= this */
228 double tolerance, /* <0 means fractional, >0 means absolute */
229 double (*func)(double *x, long *invalid),
230 void (*report)(double ymin, double *xmin, long pass, long evals, long dims),
231 long maxPasses,
232 long maxEvaluations,
233 long linMinIterations) {
234 double *xTrial, *dxLocal = NULL, **dirVector = NULL, **P = NULL;
235 double y0, dVector, denominator, merit;
236 long dir, totalEvaluations = 0, isInvalid, pass = 0;
237
238 if (!yReturn || !xGuess || !dxGuess)
239 return -1;
240 if (dims <= 0)
241 return (-3);
242
243 if (!(xTrial = malloc(sizeof(*xTrial) * dims)) ||
244 !(dxLocal = malloc(sizeof(*dxLocal) * dims)) ||
245 !(P = (double **)zarray_2d(sizeof(**P), dims + 1, dims)) ||
246 !(dirVector = (double **)zarray_2d(sizeof(**dirVector), dims, dims)))
247 bomb("memory allocation failure (powellMin)", NULL);
248 memcpy(dxLocal, dxGuess, sizeof(*dxLocal) * dims);
249
250 for (dir = 0; dir < dims; dir++) {
251 if (dxLocal[dir] == 0) {
252 if (xLowerLimit && xUpperLimit)
253 dxLocal[dir] = (xUpperLimit[dir] - xLowerLimit[dir]) / 4;
254 else if ((dxLocal[dir] = xGuess[dir] / 4) == 0)
255 dxLocal[dir] = 1;
256 }
257 if ((xLowerLimit && xUpperLimit) &&
258 (dVector = fabs(xUpperLimit[dir] - xLowerLimit[dir]) / 4) < fabs(dxLocal[dir]))
259 dxLocal[dir] = dVector;
260 }
261
262 if (xLowerLimit || xUpperLimit) {
263 /* if start is at lower/upper limit, make sure initial step is positive/negative */
264 for (dir = 0; dir < dims; dir++) {
265 if (xLowerLimit && xUpperLimit && xLowerLimit[dir] >= xUpperLimit[dir])
266 continue;
267 if (xLowerLimit && xLowerLimit[dir] >= xGuess[dir]) {
268 dxLocal[dir] = fabs(dxLocal[dir]);
269 xGuess[dir] = xLowerLimit[dir];
270 }
271 if (xUpperLimit && xUpperLimit[dir] <= xGuess[dir]) {
272 dxLocal[dir] = -fabs(dxLocal[dir]);
273 xGuess[dir] = xUpperLimit[dir];
274 }
275 }
276 }
277
278 for (dir = 0; dir < dims; dir++) {
279 P[0][dir] = xGuess[dir];
280 dirVector[dir][dir] = dxLocal[dir];
281 }
282
283 if (maxPasses <= 0)
284 maxPasses = DEFAULT_MAXPASSES;
285
286 y0 = (*func)(xGuess, &isInvalid);
287 totalEvaluations++;
288 if (isInvalid) {
289 fprintf(stderr, "Fatal error (powellMin): initial guess is invalid\n");
290 exit(1);
291 }
292 if (y0 <= target) {
293 if (report)
294 (*report)(y0, xGuess, pass, totalEvaluations, dims);
295 return (totalEvaluations);
296 }
297
298 while (pass++ < maxPasses) {
299 *yReturn = y0;
300#if DEBUG
301 fprintf(stderr, ">> Performing powell step to min %ld\n", pass);
302#endif
303 totalEvaluations += powellMinStep(yReturn, xGuess, dirVector, P,
304 xLowerLimit, xUpperLimit, dims, target, linMinIterations,
305 !pass, func);
306#if DEBUG
307 fprintf(stderr, ">> %ld evaluations total, value is %le\n",
308 totalEvaluations, *yReturn);
309#endif
310 if (tolerance <= 0) {
311 denominator = (y0 + (*yReturn)) / 2;
312 if (denominator)
313 merit = fabs(y0 - (*yReturn)) / denominator;
314 else {
315 fputs("error: divide-by-zero in fractional tolerance evaluation (powellMin)\n", stderr);
316 return -1;
317 }
318 } else
319 merit = fabs(y0 - (*yReturn));
320 if (merit <= fabs(tolerance) || (*yReturn) <= target || totalEvaluations > maxEvaluations) {
321 if (report)
322 (*report)(*yReturn, xGuess, pass, totalEvaluations, dims);
323 return (totalEvaluations);
324 }
325 if (report)
326 (*report)(*yReturn, xGuess, pass, totalEvaluations, dims);
327 y0 = *yReturn;
328 }
329#if DEBUG
330 fprintf(stderr, ">> returning from powell optimization\n");
331#endif
332 return totalEvaluations;
333}
334
void ** zarray_2d(uint64_t size, uint64_t n1, uint64_t n2)
Allocates a 2D array with specified dimensions.
Definition array.c:93
void bomb(char *error, char *usage)
Reports error messages to the terminal and aborts the program.
Definition bomb.c:26
long powellMin(double *yReturn, double *xGuess, double *dxGuess, double *xLowerLimit, double *xUpperLimit, long dims, double target, double tolerance, double(*func)(double *x, long *invalid), void(*report)(double ymin, double *xmin, long pass, long evals, long dims), long maxPasses, long maxEvaluations, long linMinIterations)
Minimizes an objective function using Powell's method.
Definition powellMin.c:220