SDDSlib
Loading...
Searching...
No Matches
onedoptimize.c
Go to the documentation of this file.
1/**
2 * @file onedoptimize.c
3 * @brief Implementation of one-dimensional optimization functions.
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, R. Soliday
14 */
15#include "mdb.h"
16
17#define DEFAULT_MAXEVALS 100
18#define DEFAULT_MAXPASSES 5
19
20long checkVariableLimits(double *x, double *xlo, double *xhi, long n);
21
22/**
23 * @brief Performs one-dimensional scan optimization on a multi-dimensional function.
24 *
25 * This function optimizes a given multi-dimensional function by performing a
26 * one-dimensional scan along each active dimension. It iteratively adjusts each
27 * variable within its specified limits to minimize the target function value.
28 * The optimization process continues until the target is achieved, the tolerance
29 * is met, or the maximum number of repeats is reached.
30 *
31 * @param yReturn Pointer to store the optimized function value.
32 * @param xGuess Initial guess for the variables.
33 * @param dxGuess Initial step sizes for each variable.
34 * @param xLowerLimit Pointer to the array of lower limits for each variable.
35 * @param xUpperLimit Pointer to the array of upper limits for each variable.
36 * @param disable Pointer to an array indicating which dimensions to disable (1 to disable, 0 to enable).
37 * @param dimensions The number of dimensions (variables) in the optimization.
38 * @param target The target function value to achieve. Optimization stops if any value is <= this.
39 * @param tolerance The tolerance for convergence. If <0, it is treated as fractional; if >0, as absolute.
40 * @param func Pointer to the function to be optimized. It takes the variables array and a pointer to an invalid flag.
41 * @param report Pointer to a reporting function that is called with the current minimum, variables, pass number, evaluations, and dimensions.
42 * @param maxSteps The maximum number of steps to try in a good direction for each scan.
43 * @param maxDivisions The maximum number of divisions (direction changes) allowed during scanning.
44 * @param maxRepeats The maximum number of optimization passes to perform.
45 * @param flags Optimization flags that modify the behavior of the optimization process.
46 * @return Returns the total number of function evaluations performed if successful, or a negative error code on failure.
47 */
49 double *yReturn,
50 double *xGuess,
51 double *dxGuess,
52 double *xLowerLimit,
53 double *xUpperLimit,
54 short *disable,
55 long dimensions,
56 double target, /* will return if any value is <= this */
57 double tolerance, /* <0 means fractional, >0 means absolute */
58 double (*func)(double *x, long *invalid),
59 void (*report)(double ymin, double *xmin, long pass, long evals, long dims),
60 long maxSteps, /* number of steps to try in good direction */
61 long maxDivisions,
62 long maxRepeats,
63 unsigned long flags) {
64 static double *dxLocal = NULL;
65 static long lastDimensions = 0;
66 double yLast, dVector = 1, yNew, xLocal;
67 long point, totalEvaluations = 0, isInvalid, repeats = 0, divisions;
68 long activeDimensions, direction, found = 0, stepsTaken, decreaseSeen;
69 double *divisor, *minimum, min;
70
71 min = 1e9;
72 if (dimensions <= 0)
73 return (-3);
74 if (disable) {
75 activeDimensions = 0;
76 for (direction = 0; direction < dimensions; direction++)
77 if (!disable[direction])
78 activeDimensions++;
79 } else
80 activeDimensions = dimensions;
81 if (activeDimensions <= 0)
82 return -3;
83
84 if (activeDimensions > lastDimensions) {
85 if (dxLocal)
86 free(dxLocal);
87 dxLocal = tmalloc(sizeof(*dxLocal) * activeDimensions);
88 lastDimensions = activeDimensions;
89 }
90 divisor = (double *)malloc(sizeof(*divisor) * activeDimensions);
91 minimum = (double *)malloc(sizeof(*minimum) * activeDimensions);
92 for (point = 0; point < activeDimensions; point++)
93 minimum[point] = target; /*important!!!!*/
94 if (!dxGuess) {
95 dxGuess = dxLocal;
96 for (direction = 0; direction < dimensions; direction++)
97 dxGuess[direction] = 0;
98 } else {
99 for (direction = 0; direction < dimensions; direction++)
100 dxLocal[direction] = dxGuess[direction];
101 }
102
103 for (direction = 0; direction < dimensions; direction++) {
104 if (dxGuess[direction] == 0) {
105 if (xLowerLimit && xUpperLimit)
106 dxGuess[direction] = (xUpperLimit[direction] - xLowerLimit[direction]) / 4;
107 else if ((dxGuess[direction] = xGuess[direction] / 4) == 0)
108 dxGuess[direction] = 1;
109 }
110 if (xLowerLimit && xUpperLimit) {
111 if ((dVector = fabs(xUpperLimit[direction] - xLowerLimit[direction]) / 4) < fabs(dxGuess[direction]))
112 dxGuess[direction] = dVector;
113 }
114 if (disable && disable[direction])
115 dxGuess[direction] = 0;
116 }
117
118 if (xLowerLimit) {
119 /* if start is at lower limit, make sure initial step is positive */
120 for (direction = 0; direction < dimensions; direction++)
121 if (xLowerLimit[direction] >= xGuess[direction])
122 dxGuess[direction] = fabs(dxGuess[direction]);
123 }
124 if (xUpperLimit) {
125 /* if start is at upper limit, make sure initial step is negative */
126 for (direction = 0; direction < dimensions; direction++)
127 if (xUpperLimit[direction] <= xGuess[direction])
128 dxGuess[direction] = -fabs(dxGuess[direction]);
129 }
130#if DEBUG
131 for (direction = 0; direction < dimensions; direction++)
132 fprintf(stderr, "direction %ld: guess=%le delta=%le disable=%hd\n",
133 direction, xGuess[direction], dxGuess[direction],
134 disable ? disable[direction] : (short)0);
135#endif
136
137 if (maxRepeats <= 0)
138 maxRepeats = DEFAULT_MAXPASSES;
139 yLast = (*func)(xGuess, &isInvalid);
140 totalEvaluations++;
141 if (isInvalid) {
142 fprintf(stderr, "error: initial guess is invalid in simplexMin()\n");
143 return (-3);
144 }
145 if (yLast <= target) {
146 *yReturn = yLast;
147 if (report)
148 (*report)(*yReturn, xGuess, repeats, totalEvaluations, dimensions);
149 return (totalEvaluations);
150 }
151 for (point = 0; point < activeDimensions; point++)
152 divisor[point] = 1;
153 while (repeats < maxRepeats) {
154 repeats++;
155 for (point = 0; point < activeDimensions; point++) {
156 /* fprintf(stderr,"scan the %ldth variable\n",point+1); */
157 divisions = 0;
158 found = 0;
159 xLocal = xGuess[point];
160 while (divisions < maxDivisions) {
161 stepsTaken = 0;
162 decreaseSeen = 0;
163 while (stepsTaken < maxSteps) {
164 xGuess[point] = xGuess[point] + dxGuess[point] / divisor[point];
165 if ((xLowerLimit || xUpperLimit) &&
166 !checkVariableLimits(xGuess, xLowerLimit, xUpperLimit, dimensions)) {
167#if DEBUG
168 long idum;
169 fprintf(stderr, " Point outside of bounds:\n");
170 for (idum = 0; idum < dimensions; idum++)
171 fprintf(stderr, " %le %le, %le\n", xGuess[idum],
172 xLowerLimit[idum], xUpperLimit[idum]);
173#endif
174 yNew = yLast * 1e9;
175 /* breaks us out of this loop */
176 stepsTaken = maxSteps;
177 } else {
178 /* measure the function value */
179 yNew = (*func)(xGuess, &isInvalid);
180 totalEvaluations++;
181 if (isInvalid) {
182#if DEBUG
183 fprintf(stderr, " Point is invalid\n");
184#endif
185 yNew = yLast * 1e9;
186 }
187 if (yNew <= target) {
188 /* if the value reaches target, success */
189 if (yNew <= yLast)
190 *yReturn = yNew;
191 else {
192 *yReturn = yLast;
193 xGuess[point] = xLocal;
194 }
195 if (report)
196 (*report)(*yReturn, xGuess, repeats, totalEvaluations, dimensions);
197 return (totalEvaluations);
198 }
199 if (fabs(yNew - yLast) <= tolerance) {
200 /* if the difference reaches tolerance, reach the local minimum */
201 if (yNew <= yLast)
202 minimum[point] = yNew;
203 else {
204 minimum[point] = yLast;
205 xGuess[point] = xLocal;
206 }
207 found = 1;
208 /* fprintf(stderr,"min[%ld]=%e\n",point,minimum[point]); */
209 if (fabs(minimum[point] - min) <= tolerance) {
210 /* if the difference of two minimums reaches tolerance, success */
211 *yReturn = minimum[point];
212 if (report)
213 (*report)(*yReturn, xGuess, repeats, totalEvaluations, dimensions);
214 return (totalEvaluations);
215 } else
216 break;
217 }
218 if (yNew <= yLast) {
219 /*decrease found*/
220#ifdef DEBUG
221 fprintf(stderr, "decrease found, yNew=%le \n", yNew);
222#endif
223 yLast = yNew;
224 xLocal = xGuess[point];
225 decreaseSeen = 1;
226 } else {
227 /* new value is greater than the old, set the PVs back
228 * and break out of this loop. Will result (normally)
229 * in re-entering the loop with opposite sign on step size
230 */
231 xGuess[point] = xLocal;
232 stepsTaken = maxSteps;
233#ifdef DEBUG
234 fprintf(stderr, "Increase seen: setting back to %e\n", xLocal);
235#endif
236 if (flags & ONEDSCANOPTIMIZE_REFRESH) {
237 yLast = (*func)(xGuess, &isInvalid); /*read the measurements again */
238 totalEvaluations++;
239 }
240 if (isInvalid) {
241 fprintf(stderr, "error: initial guess is invalid in OneDScanOptimize()\n");
242 return (-3);
243 }
244 }
245 } /* end of branch for trial points that are within limits */
246 stepsTaken++;
247 } /* end of loop over allowed steps in one direction */
248 divisions++;
249 if ((divisions % 2))
250 /* reverse directions */
251 divisor[point] *= decreaseSeen ? -2 : -1;
252 else
253 /* decrease the step size */
254 divisor[point] *= 3;
255 } /* end of "while (divisions<maxDivisions)" */
256 dxGuess[point] /= 2; /*decrease the delta by 2 for next cycle */
257 } /*end of "for (point=0;point<activeDimensions;point++)" */
258 if (found) {
259 if (minimum[activeDimensions - 1] < min)
260 min = minimum[activeDimensions - 1];
261 /* fprintf(stderr,"min=%e\n",min); */
262 }
263 } /*end of "while (repeats<maxRepeats)" */
264 *yReturn = yLast;
265 if (*yReturn <= target) {
266 if (report)
267 (*report)(*yReturn, xGuess, repeats, totalEvaluations, dimensions);
268 return (totalEvaluations);
269 } else {
270 if (report)
271 (*report)(*yReturn, xGuess, repeats, totalEvaluations, dimensions);
272 /* fprintf(stderr,"The minimum (may not be the best) is %le after %ld cycles of 1dscan.\n",
273 *yReturn, repeats); */
274 return 0;
275 }
276}
277
278/**
279 * @brief Optimizes a single-variable function using parabolic interpolation.
280 *
281 * This function performs optimization (minimization or maximization) of a single-variable
282 * function using parabolic interpolation. It iteratively adjusts the variable to find
283 * the function's minimum or maximum by fitting a parabola to three points and locating
284 * its vertex. The optimization continues until convergence criteria based on step size
285 * or function value tolerance are met, or until the maximum number of cycles is reached.
286 *
287 * @param yReturn Pointer to store the optimized function value.
288 * @param xGuess Initial guess for the variable.
289 * @param dx Initial step size for searching.
290 * @param xLower The lower bound for the variable.
291 * @param xUpper The upper bound for the variable.
292 * @param func Pointer to the function to be optimized. It takes the variable and a pointer to an invalid flag.
293 * @param maxCycles The maximum number of optimization cycles to perform.
294 * @param dxLimit The minimum step size below which the optimization will stop.
295 * @param tolerance The tolerance for convergence based on the difference in function values.
296 * @param maximize If non-zero, the function will perform maximization instead of minimization.
297 * @return Returns 1 on successful optimization, or a negative error code on failure.
298 */
299long OneDParabolicOptimization(double *yReturn, double *xGuess, double dx,
300 double xLower, double xUpper,
301 double (*func)(double x, long *invalid),
302 long maxCycles, double dxLimit,
303 double tolerance, long maximize) {
304 double maxFactor, fBest, xBest;
305 long invalid, cycle;
306 double x0, x1 = 0, x2, f0, f1 = 0, f2;
307
308 maxFactor = maximize ? -1 : 1;
309 invalid = 0;
310
311 x0 = *xGuess;
312 f0 = maxFactor * (*func)(x0, &invalid);
313 xBest = x0;
314 fBest = f0;
315
316 *yReturn = maxFactor * f0;
317 if (invalid)
318 return -1;
319
320 /* find direction in which function decreases */
321 for (cycle = 0; cycle < 2 * maxCycles; cycle++) {
322 x1 = x0 + dx;
323 if (x1 == x0)
324 break;
325 if (x1 > xUpper || x1 < xLower)
326 return -2;
327 f1 = maxFactor * (*func)(x1, &invalid);
328 if (invalid)
329 return -1;
330#ifdef DEBUG
331 fprintf(stderr, "cycle = %ld, maxCycles=%ld, f1 = %21.15e, fBest = %21.15e\n",
332 cycle, 2 * maxCycles, f1, fBest);
333#endif
334 if (f1 < fBest) {
335#ifdef DEBUG
336 fprintf(stderr, "f1<fBest\n");
337#endif
338 fBest = f1;
339 xBest = x1;
340 }
341 if (f1 < f0) {
342#ifdef DEBUG
343 fprintf(stderr, "f1<f0, breaking\n");
344#endif
345 break;
346 }
347 dx = dx * (cycle % 2 == 0 ? -1 : -0.5);
348 }
349 if (x1 == x0)
350 return 1;
351 if (cycle == 2 * maxCycles) {
352 if (fabs(dx) < dxLimit)
353 return 1;
354 return -3;
355 }
356#ifdef DEBUG
357 fprintf(stderr, "Function decreases with dx=%e, f0=%21.15e, f1=%21.15e, cycle=%ld\n", dx, f0, f1, cycle);
358#endif
359
360 /* take steps until passing through minimum */
361 while (1) {
362 x2 = x1 + dx;
363 if (x2 > xUpper || x2 < xLower)
364 return -4;
365 f2 = maxFactor * (*func)(x2, &invalid);
366 if (invalid)
367 return -1;
368 if (f2 < fBest) {
369 fBest = f2;
370 xBest = x2;
371 }
372#ifdef DEBUG
373 fprintf(stderr, "fBest = %21.15e, f1 = %21.15e, f2 = %21.15e\n",
374 fBest, f1, f2);
375#endif
376 if (f2 > f1)
377 break;
378 if (x1 == x2)
379 break;
380 x0 = x1;
381 f0 = f1;
382 x1 = x2;
383 f1 = f2;
384 }
385 if (x0 > x2) {
386 /* arrange in increasing order */
387 SWAP_DOUBLE(x0, x2);
388 SWAP_DOUBLE(f0, f2);
389 }
390
391 /* now f0 > f1 and f2 > f1 */
392 for (cycle = 0; cycle < maxCycles; cycle++) {
393 double numer, denom, x3, f3, scale;
394 long failed;
395#ifdef DEBUG
396 fprintf(stderr, "Cycle %ld: f(%e)=%e, f(%e)=%e, f(%e)=%e\n",
397 cycle, x0, f0, x1, f1, x2, f2);
398#endif
399
400 if (x2 == x0 || (x2 - x0) < dxLimit || (MAX(f2, f0) - f1) < tolerance)
401 break;
402 /* try parabolic interpolation */
403 numer = sqr(x1 - x0) * (f1 - f2) - sqr(x1 - x2) * (f1 - f0);
404 denom = (x1 - x0) * (f1 - f2) - (x1 - x2) * (f1 - f0);
405 x3 = x1 - numer / denom / 2.0;
406 failed = 1;
407 scale = x2 - x0;
408#ifdef DEBUG
409 fprintf(stderr, "parabolic parameters: x3 = %e, f3 = %e, scale=%e, x0=%e, x2=%e\n",
410 x3,
411 isinf(x3) ? DBL_MAX : maxFactor * (*func)(x3, &invalid), scale, x0, x2);
412#endif
413 if (!isinf(x3) && x0 < x3 && x3 < x2 &&
414 fabs(x3 - x0) > 1e-6 * scale && fabs(x3 - x1) > 1e-6 * scale &&
415 fabs(x3 - x2) > 1e-6 * scale) {
416 /* evaluate at parabolic interpolation point */
417 failed = 0;
418 f3 = maxFactor * (*func)(x3, &invalid);
419 if (invalid)
420 failed = 1;
421 else {
422 if (f3 < fBest) {
423 fBest = f3;
424 xBest = x3;
425 }
426 if (f3 < f1) {
427 /* replace point 1 */
428 f1 = f3;
429 x1 = x3;
430 } else if (f2 > f0 && f3 < f2) {
431 /* replace point 2 */
432 f2 = f3;
433 x2 = x3;
434 if (x2 < x1) {
435 SWAP_DOUBLE(x1, x2);
436 SWAP_DOUBLE(f1, f2);
437 }
438 } else if (f2 < f0 && f3 < f0) {
439 /* replace point 0 */
440 f0 = f3;
441 x0 = x3;
442 if (x0 > x1) {
443 SWAP_DOUBLE(x0, x1);
444 SWAP_DOUBLE(f0, f1);
445 }
446 } else
447 failed = 1;
448 }
449 }
450#ifdef DEBUG
451 if (!failed)
452 fprintf(stderr, "Parabolic interpolation succeeded\n");
453#endif
454 if (failed) {
455 long right, other;
456 for (other = 0; other < 2; other++) {
457 /* try dividing one of the intervals */
458 if (fabs(x0 - x1) < fabs(x1 - x2)) {
459 if (!other) {
460 x3 = (x1 + x2) / 2;
461 right = 1;
462 } else {
463 x3 = (x0 + x1) / 2;
464 right = 0;
465 }
466 } else {
467 if (!other) {
468 x3 = (x0 + x1) / 2;
469 right = 0;
470 } else {
471 x3 = (x1 + x2) / 2;
472 right = 1;
473 }
474 }
475 f3 = maxFactor * (*func)(x3, &invalid);
476 if (invalid)
477 return -1;
478 if (f3 < fBest) {
479 fBest = f3;
480 xBest = x3;
481 }
482#ifdef DEBUG
483 fprintf(stderr, "f3 = %e at x3=%e\n", f3, x3);
484#endif
485 if (f3 < f1) {
486#ifdef DEBUG
487 fprintf(stderr, "Replacing point 1\n");
488#endif
489 f1 = f3;
490 x1 = x3;
491 break;
492 }
493 if (right && f3 < f2) {
494 /* replace point 2 */
495#ifdef DEBUG
496 fprintf(stderr, "Replacing point 2\n");
497#endif
498 f2 = f3;
499 x2 = x3;
500 if (x2 < x1) {
501 SWAP_DOUBLE(x1, x2);
502 SWAP_DOUBLE(f1, f2);
503 }
504 break;
505 } else if (!right && f3 < f0) {
506 /* replace point 0 */
507#ifdef DEBUG
508 fprintf(stderr, "Replacing point 0\n");
509#endif
510 f0 = f3;
511 x0 = x3;
512 if (x0 > x1) {
513 SWAP_DOUBLE(x0, x1);
514 SWAP_DOUBLE(f0, f1);
515 }
516 break;
517 }
518 }
519#ifdef DEBUG
520 fprintf(stderr, "Sectioning succeeded\n");
521#endif
522 }
523 }
524#ifdef DEBUG
525 fprintf(stderr, "Returning: x=%21.15e, y=%21.15e\n",
526 xBest, maxFactor * fBest);
527#endif
528 *yReturn = maxFactor * fBest;
529 *xGuess = xBest;
530 return 1;
531}
void * tmalloc(uint64_t size_of_block)
Allocates a memory block of the specified size with zero initialization.
Definition array.c:59
long OneDScanOptimize(double *yReturn, double *xGuess, double *dxGuess, double *xLowerLimit, double *xUpperLimit, short *disable, long dimensions, double target, double tolerance, double(*func)(double *x, long *invalid), void(*report)(double ymin, double *xmin, long pass, long evals, long dims), long maxSteps, long maxDivisions, long maxRepeats, unsigned long flags)
Performs one-dimensional scan optimization on a multi-dimensional function.
long OneDParabolicOptimization(double *yReturn, double *xGuess, double dx, double xLower, double xUpper, double(*func)(double x, long *invalid), long maxCycles, double dxLimit, double tolerance, long maximize)
Optimizes a single-variable function using parabolic interpolation.