5 #include "nlopt-util.h"
11 #define MIN(a,b) ((a) < (b) ? (a) : (b))
12 #define MAX(a,b) ((a) > (b) ? (a) : (b))
14 /***************************************************************************/
15 /* basic data structure:
17 * a hyper-rectangle is stored as an array of length L = 2n+2, where [1]
18 * is the value (f) of the function at the center, [0] is the "size"
19 * measure (d) of the rectangle, [2..n+1] are the coordinates of the
20 * center (c), and [n+2..2n+1] are the widths of the sides (w).
22 * we store the hyper-rectangles in a red-black tree, sorted by (d,f)
23 * in lexographic order, to allow us to perform quick convex-hull
24 * calculations (in the future, we might make this data structure
25 * more sophisticated based on the dynamic convex-hull literature).
30 #define RECT_LEN(n) (2*(n)+2) /* number of double values in a hyperrect */
32 /* parameters of the search algorithm and various information that
33 needs to be passed around */
35 int n; /* dimension */
36 int L; /* RECT_LEN(n) */
37 double magic_eps; /* Jones' epsilon parameter (1e-4 is recommended) */
38 int which_diam; /* which measure of hyper-rectangle diam to use:
39 0 = Jones, 1 = Gablonsky */
40 int which_div; /* which way to divide rects:
41 0: Gablonsky (cubes divide all, rects longest)
42 1: orig. Jones (divide all longest sides)
43 2: Jones Encyc. Opt.: pick random longest side */
44 const double *lb, *ub;
45 nlopt_stopping *stop; /* stopping criteria */
46 nlopt_func f; void *f_data;
47 double *work; /* workspace, of length >= 2*n */
48 int *iwork; /* workspace, length >= n */
49 double fmin, *xmin; /* minimum so far */
51 /* red-black tree of hyperrects, sorted by (d,f) in
52 lexographical order */
54 double **hull; /* array to store convex hull */
55 int hull_len; /* allocated length of hull array */
58 /***************************************************************************/
60 /* Evaluate the "diameter" (d) of a rectangle of widths w[n]
62 We round the result to single precision, which should be plenty for
63 the use we put the diameter to (rect sorting), to allow our
64 performance hack in convex_hull to work (in the Jones and Gablonsky
65 DIRECT algorithms, all of the rects fall into a few diameter
66 values, and we don't want rounding error to spoil this) */
67 static double rect_diameter(int n, const double *w, const params *p)
70 if (p->which_diam == 0) { /* Jones measure */
72 for (i = 0; i < n; ++i)
74 /* distance from center to a vertex */
75 return ((float) (sqrt(sum) * 0.5));
77 else { /* Gablonsky measure */
79 for (i = 0; i < n; ++i)
82 /* half-width of longest side */
83 return ((float) (maxw * 0.5));
87 #define ALLOC_RECT(rect, L) if (!(rect = (double*) malloc(sizeof(double)*(L)))) return NLOPT_OUT_OF_MEMORY
89 static double *fv_qsort = 0;
90 static int sort_fv_compare(const void *a_, const void *b_)
92 int a = *((const int *) a_), b = *((const int *) b_);
93 double fa = MIN(fv_qsort[2*a], fv_qsort[2*a+1]);
94 double fb = MIN(fv_qsort[2*b], fv_qsort[2*b+1]);
102 static void sort_fv(int n, double *fv, int *isort)
105 for (i = 0; i < n; ++i) isort[i] = i;
106 fv_qsort = fv; /* not re-entrant, sigh... */
107 qsort(isort, (unsigned) n, sizeof(int), sort_fv_compare);
111 static double function_eval(const double *x, params *p) {
112 double f = p->f(p->n, x, NULL, p->f_data);
115 memcpy(p->xmin, x, sizeof(double) * p->n);
120 #define FUNCTION_EVAL(fv,x,p,freeonerr) fv = function_eval(x, p); if (p->fmin < p->stop->fmin_max) { free(freeonerr); return NLOPT_FMIN_MAX_REACHED; } else if (nlopt_stop_evals((p)->stop)) { free(freeonerr); return NLOPT_MAXEVAL_REACHED; } else if (nlopt_stop_time((p)->stop)) { free(freeonerr); return NLOPT_MAXTIME_REACHED; }
122 #define THIRD (0.3333333333333333333333)
124 #define EQUAL_SIDE_TOL 5e-2 /* tolerance to equate side sizes */
126 /* divide rectangle idiv in the list p->rects */
127 static nlopt_result divide_rect(double *rdiv, params *p)
130 const const int n = p->n;
132 double *c = rdiv + 2; /* center of rect to divide */
133 double *w = c + n; /* widths of rect to divide */
135 int imax = 0, nlongest = 0;
138 for (i = 1; i < n; ++i)
141 for (i = 0; i < n; ++i)
142 if (wmax - w[i] <= wmax * EQUAL_SIDE_TOL)
144 if (p->which_div == 1 || (p->which_div == 0 && nlongest == n)) {
145 /* trisect all longest sides, in increasing order of the average
146 function value along that direction */
147 double *fv = p->work;
148 int *isort = p->iwork;
149 for (i = 0; i < n; ++i) {
150 if (wmax - w[i] <= wmax * EQUAL_SIDE_TOL) {
152 c[i] = csave - w[i] * THIRD;
153 FUNCTION_EVAL(fv[2*i], c, p, 0);
154 c[i] = csave + w[i] * THIRD;
155 FUNCTION_EVAL(fv[2*i+1], c, p, 0);
159 fv[2*i] = fv[2*i+1] = HUGE_VAL;
162 sort_fv(n, fv, isort);
163 if (!(node = rb_tree_find(&p->rtree, rdiv)))
164 return NLOPT_FAILURE;
165 for (i = 0; i < nlongest; ++i) {
167 w[isort[i]] *= THIRD;
168 rdiv[0] = rect_diameter(n, w, p);
169 node = rb_tree_resort(&p->rtree, node);
170 for (k = 0; k <= 1; ++k) {
173 memcpy(rnew, rdiv, sizeof(double) * L);
174 rnew[2 + isort[i]] += w[isort[i]] * (2*k-1);
175 rnew[1] = fv[2*isort[i]+k];
176 if (!rb_tree_insert(&p->rtree, rnew)) {
178 return NLOPT_OUT_OF_MEMORY;
185 if (nlongest > 1 && p->which_div == 2) {
186 /* randomly choose longest side */
187 i = nlopt_iurand(nlongest);
188 for (k = 0; k < n; ++k)
189 if (wmax - w[k] <= wmax * EQUAL_SIDE_TOL) {
190 if (!i) { i = k; break; }
195 i = imax; /* trisect longest side */
196 if (!(node = rb_tree_find(&p->rtree, rdiv)))
197 return NLOPT_FAILURE;
199 rdiv[0] = rect_diameter(n, w, p);
200 node = rb_tree_resort(&p->rtree, node);
201 for (k = 0; k <= 1; ++k) {
204 memcpy(rnew, rdiv, sizeof(double) * L);
205 rnew[2 + i] += w[i] * (2*k-1);
206 FUNCTION_EVAL(rnew[1], rnew + 2, p, rnew);
207 if (!rb_tree_insert(&p->rtree, rnew)) {
209 return NLOPT_OUT_OF_MEMORY;
213 return NLOPT_SUCCESS;
216 /***************************************************************************/
217 /* Convex hull algorithm, used later to find the potentially optimal
218 points. What we really have in DIRECT is a "dynamic convex hull"
219 problem, since we are dynamically adding/removing points and
220 updating the hull, but I haven't implemented any of the fancy
221 algorithms for this problem yet. */
223 /* Find the lower convex hull of a set of points (x,y) stored in a rb-tree
224 of pointers to {x,y} arrays sorted in lexographic order by (x,y).
226 Unlike standard convex hulls, we allow redundant points on the hull.
228 The return value is the number of points in the hull, with pointers
229 stored in hull[i] (should be an array of length >= t->N).
231 static int convex_hull(rb_tree *t, double **hull)
235 double xmin, xmax, yminmin, ymaxmin;
238 /* Monotone chain algorithm [Andrew, 1979]. */
242 nmax = rb_tree_max(t);
248 hull[nhull++] = n->k;
249 if (xmin == xmax) return nhull;
251 /* set nmax = min mode with x == xmax */
253 while (nmax->k[0] == xmax)
254 nmax = rb_tree_pred(nmax); /* non-NULL since xmin != xmax */
255 nmax = rb_tree_succ(nmax);
257 /* performance hack (see also below) */
260 kshift[0] = xmax * (1 - 1e-13);
261 kshift[1] = -HUGE_VAL;
262 nmax = rb_tree_find_gt(t, kshift); /* non-NULL since xmin != xmax */
266 ymaxmin = nmax->k[1];
267 minslope = (ymaxmin - yminmin) / (xmax - xmin);
269 /* set n = first node with x != xmin */
271 while (n->k[0] == xmin)
272 n = rb_tree_succ(n); /* non-NULL since xmin != xmax */
274 /* performance hack (see also below) */
277 kshift[0] = xmin * (1 + 1e-13);
278 kshift[1] = -HUGE_VAL;
279 n = rb_tree_find_gt(t, kshift); /* non-NULL since xmin != xmax */
283 for (; n != nmax; n = rb_tree_succ(n)) {
285 if (k[1] > yminmin + (k[0] - xmin) * minslope)
288 /* performance hack: most of the points in DIRECT lie along
289 vertical lines at a few x values, and we can exploit this */
290 if (nhull && k[0] == hull[nhull - 1][0]) { /* x == previous x */
291 if (k[1] > hull[nhull - 1][1]) {
293 /* because of the round to float in rect_diameter, above,
294 it shouldn't be possible for two diameters (x values)
295 to have a fractional difference < 1e-13. Note
296 that k[0] > 0 always in DIRECT */
297 kshift[0] = k[0] * (1 + 1e-13);
298 kshift[1] = -HUGE_VAL;
299 n = rb_tree_pred(rb_tree_find_gt(t, kshift));
302 else { /* equal y values, add to hull */
308 /* remove points until we are making a "left turn" to k */
310 double *t1 = hull[nhull - 1], *t2;
312 /* because we allow equal points in our hull, we have
313 to modify the standard convex-hull algorithm slightly:
314 we need to look backwards in the hull list until we
315 find a point t2 != t1 */
319 } while (it2 >= 0 && t2[0] == t1[0] && t2[1] == t1[1]);
322 /* cross product (t1-t2) x (k-t2) > 0 for a left turn: */
323 if ((t1[0]-t2[0]) * (k[1]-t2[1])
324 - (t1[1]-t2[1]) * (k[0]-t2[0]) >= 0)
330 hull[nhull++] = nmax->k;
334 /***************************************************************************/
336 static int small(double *w, params *p)
339 for (i = 0; i < p->n; ++i)
340 if (w[i] > p->stop->xtol_abs[i] &&
341 w[i] > (p->ub[i] - p->lb[i]) * p->stop->xtol_rel)
346 static nlopt_result divide_good_rects(params *p)
350 int nhull, i, xtol_reached = 1, divided_some = 0;
351 double magic_eps = p->magic_eps;
353 if (p->hull_len < p->rtree.N) {
354 p->hull_len += p->rtree.N;
355 p->hull = (double **) realloc(p->hull, sizeof(double*)*p->hull_len);
356 if (!p->hull) return NLOPT_OUT_OF_MEMORY;
358 nhull = convex_hull(&p->rtree, hull = p->hull);
360 for (i = 0; i < nhull; ++i) {
361 double K1 = -HUGE_VAL, K2 = -HUGE_VAL, K;
363 K1 = (hull[i][1] - hull[i-1][1]) / (hull[i][0] - hull[i-1][0]);
365 K1 = (hull[i][1] - hull[i+1][1]) / (hull[i][0] - hull[i+1][0]);
367 if (hull[i][1] - K * hull[i][0]
368 <= p->fmin - magic_eps * fabs(p->fmin)) {
369 /* "potentially optimal" rectangle, so subdivide */
371 nlopt_result ret = divide_rect(hull[i], p);
372 if (ret != NLOPT_SUCCESS) return ret;
373 xtol_reached = xtol_reached && small(hull[i] + 2+n, p);
377 if (magic_eps != 0) {
379 goto divisions; /* try again */
381 else { /* WTF? divide largest rectangle with smallest f */
382 /* (note that this code actually gets called from time
383 to time, and the heuristic here seems to work well,
384 but I don't recall this situation being discussed in
386 rb_node *max = rb_tree_max(&p->rtree);
388 double wmax = max->k[0];
389 do { /* note: this loop is O(N) worst-case time */
391 pred = rb_tree_pred(max);
392 } while (pred && pred->k[0] == wmax);
393 return divide_rect(max->k, p);
396 return xtol_reached ? NLOPT_XTOL_REACHED : NLOPT_SUCCESS;
399 /***************************************************************************/
401 /* lexographic sort order (d,f) of hyper-rects, for red-black tree */
402 static int hyperrect_compare(double *a, double *b)
404 if (a[0] < b[0]) return -1;
405 if (a[0] > b[0]) return +1;
406 if (a[1] < b[1]) return -1;
407 if (a[1] > b[1]) return +1;
408 return (int) (a - b); /* tie-breaker */
411 /***************************************************************************/
413 nlopt_result cdirect_unscaled(int n, nlopt_func f, void *f_data,
414 const double *lb, const double *ub,
417 nlopt_stopping *stop,
418 double magic_eps, int which_alg)
423 nlopt_result ret = NLOPT_OUT_OF_MEMORY;
425 p.magic_eps = magic_eps;
426 p.which_diam = which_alg % 2;
428 p.lb = lb; p.ub = ub;
435 p.fmin = f(n, x, NULL, f_data); stop->nevals++;
440 rb_tree_init(&p.rtree, hyperrect_compare);
442 p.work = (double *) malloc(sizeof(double) * (2*n));
443 if (!p.work) goto done;
444 p.iwork = (int *) malloc(sizeof(int) * n);
445 if (!p.iwork) goto done;
446 p.hull_len = 128; /* start with a reasonable number */
447 p.hull = (double **) malloc(sizeof(double *) * p.hull_len);
448 if (!p.hull) goto done;
450 if (!(rnew = (double *) malloc(sizeof(double) * p.L))) goto done;
451 for (i = 0; i < n; ++i) {
452 rnew[2+i] = 0.5 * (lb[i] + ub[i]);
454 && (fabs(rnew[2+i]-x[i]) < 1e-13*(1+fabs(x[i])));
455 rnew[2+n+i] = ub[i] - lb[i];
457 rnew[0] = rect_diameter(n, rnew+2+n, &p);
459 rnew[1] = p.fmin; /* avoid computing f(center) twice */
461 rnew[1] = function_eval(rnew+2, &p);
462 if (!rb_tree_insert(&p.rtree, rnew)) {
467 ret = divide_rect(rnew, &p);
468 if (ret != NLOPT_SUCCESS) goto done;
471 double fmin0 = p.fmin;
472 ret = divide_good_rects(&p);
473 if (ret != NLOPT_SUCCESS) goto done;
474 if (p.fmin < fmin0 && nlopt_stop_f(p.stop, p.fmin, fmin0)) {
475 ret = NLOPT_FTOL_REACHED;
481 rb_tree_destroy_with_keys(&p.rtree);
490 /* in the conventional DIRECT-type algorithm, we first rescale our
491 coordinates to a unit hypercube ... we do this simply by
492 wrapping cdirect() around cdirect_unscaled(). */
498 const double *lb, *ub;
500 static double uf(int n, const double *xu, double *grad, void *d_)
502 uf_data *d = (uf_data *) d_;
505 for (i = 0; i < n; ++i)
506 d->x[i] = d->lb[i] + xu[i] * (d->ub[i] - d->lb[i]);
507 f = d->f(n, d->x, grad, d->f_data);
509 for (i = 0; i < n; ++i)
510 grad[i] *= d->ub[i] - d->lb[i];
514 nlopt_result cdirect(int n, nlopt_func f, void *f_data,
515 const double *lb, const double *ub,
518 nlopt_stopping *stop,
519 double magic_eps, int which_alg)
523 const double *xtol_abs_save;
526 d.f = f; d.f_data = f_data; d.lb = lb; d.ub = ub;
527 d.x = (double *) malloc(sizeof(double) * n*4);
528 if (!d.x) return NLOPT_OUT_OF_MEMORY;
530 for (i = 0; i < n; ++i) {
531 x[i] = (x[i] - lb[i]) / (ub[i] - lb[i]);
534 d.x[3*n+i] = stop->xtol_abs[i] / (ub[i] - lb[i]);
536 xtol_abs_save = stop->xtol_abs;
537 stop->xtol_abs = d.x + 3*n;
538 ret = cdirect_unscaled(n, uf, &d, d.x+n, d.x+2*n, x, fmin, stop,
539 magic_eps, which_alg);
540 stop->xtol_abs = xtol_abs_save;
541 for (i = 0; i < n; ++i)
542 x[i] = lb[i]+ x[i] * (ub[i] - lb[i]);