chiark / gitweb /
copyright updates
[nlopt.git] / cdirect / cdirect.c
1 /* Copyright (c) 2007-2008 Massachusetts Institute of Technology
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  * 
11  * The above copyright notice and this permission notice shall be
12  * included in all copies or substantial portions of the Software.
13  * 
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
21  */
22
23 #include <math.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "nlopt-util.h"
28 #include "nlopt.h"
29 #include "cdirect.h"
30 #include "redblack.h"
31 #include "config.h"
32
33 #define MIN(a,b) ((a) < (b) ? (a) : (b))
34 #define MAX(a,b) ((a) > (b) ? (a) : (b))
35
36 /***************************************************************************/
37 /* basic data structure:
38  *
39  * a hyper-rectangle is stored as an array of length L = 2n+3, where [1]
40  * is the value (f) of the function at the center, [0] is the "size"
41  * measure (d) of the rectangle, [3..n+2] are the coordinates of the
42  * center (c), [n+3..2n+2] are the widths of the sides (w), and [2]
43  * is an "age" measure for tie-breaking purposes.
44  *
45  * we store the hyper-rectangles in a red-black tree, sorted by (d,f)
46  * in lexographic order, to allow us to perform quick convex-hull
47  * calculations (in the future, we might make this data structure
48  * more sophisticated based on the dynamic convex-hull literature).
49  *
50  * n > 0 always, of course.
51  */
52
53 /* parameters of the search algorithm and various information that
54    needs to be passed around */
55 typedef struct {
56      int n; /* dimension */
57      int L; /* size of each rectangle (2n+3) */
58      double magic_eps; /* Jones' epsilon parameter (1e-4 is recommended) */
59      int which_diam; /* which measure of hyper-rectangle diam to use:
60                         0 = Jones, 1 = Gablonsky */
61      int which_div; /* which way to divide rects:
62                        0: orig. Jones (divide all longest sides)
63                        1: Gablonsky (cubes divide all, rects longest)
64                        2: Jones Encyc. Opt.: pick random longest side */
65      int which_opt; /* which rects are considered "potentially optimal"
66                        0: Jones (all pts on cvx hull, even equal pts)
67                        1: Gablonsky DIRECT-L (pick one pt, if equal pts)
68                        2: ~ 1, but pick points randomly if equal pts 
69                     ... 2 seems to suck compared to just picking oldest pt */
70   
71      const double *lb, *ub;
72      nlopt_stopping *stop; /* stopping criteria */
73      nlopt_func f; void *f_data;
74      double *work; /* workspace, of length >= 2*n */
75      int *iwork; /* workspace, length >= n */
76      double minf, *xmin; /* minimum so far */
77      
78      /* red-black tree of hyperrects, sorted by (d,f,age) in
79         lexographical order */
80      rb_tree rtree;
81      int age; /* age for next new rect */
82      double **hull; /* array to store convex hull */
83      int hull_len; /* allocated length of hull array */
84 } params;
85
86 /***************************************************************************/
87
88 /* Evaluate the "diameter" (d) of a rectangle of widths w[n] 
89
90    We round the result to single precision, which should be plenty for
91    the use we put the diameter to (rect sorting), to allow our
92    performance hack in convex_hull to work (in the Jones and Gablonsky
93    DIRECT algorithms, all of the rects fall into a few diameter
94    values, and we don't want rounding error to spoil this) */
95 static double rect_diameter(int n, const double *w, const params *p)
96 {
97      int i;
98      if (p->which_diam == 0) { /* Jones measure */
99           double sum = 0;
100           for (i = 0; i < n; ++i)
101                sum += w[i] * w[i];
102           /* distance from center to a vertex */
103           return ((float) (sqrt(sum) * 0.5)); 
104      }
105      else { /* Gablonsky measure */
106           double maxw = 0;
107           for (i = 0; i < n; ++i)
108                if (w[i] > maxw)
109                     maxw = w[i];
110           /* half-width of longest side */
111           return ((float) (maxw * 0.5));
112      }
113 }
114
115 #define ALLOC_RECT(rect, L) if (!(rect = (double*) malloc(sizeof(double)*(L)))) return NLOPT_OUT_OF_MEMORY
116
117 static double *fv_qsort = 0;
118 static int sort_fv_compare(const void *a_, const void *b_)
119 {
120      int a = *((const int *) a_), b = *((const int *) b_);
121      double fa = MIN(fv_qsort[2*a], fv_qsort[2*a+1]);
122      double fb = MIN(fv_qsort[2*b], fv_qsort[2*b+1]);
123      if (fa < fb)
124           return -1;
125      else if (fa > fb)
126           return +1;
127      else
128           return 0;
129 }
130 static void sort_fv(int n, double *fv, int *isort)
131 {
132      int i;
133      for (i = 0; i < n; ++i) isort[i] = i;
134      fv_qsort = fv; /* not re-entrant, sigh... */
135      qsort(isort, (unsigned) n, sizeof(int), sort_fv_compare);
136      fv_qsort = 0;
137 }
138
139 static double function_eval(const double *x, params *p) {
140      double f = p->f(p->n, x, NULL, p->f_data);
141      if (f < p->minf) {
142           p->minf = f;
143           memcpy(p->xmin, x, sizeof(double) * p->n);
144      }
145      p->stop->nevals++;
146      return f;
147 }
148 #define FUNCTION_EVAL(fv,x,p,freeonerr) fv = function_eval(x, p); if (p->minf < p->stop->minf_max) { free(freeonerr); return NLOPT_MINF_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; }
149
150 #define THIRD (0.3333333333333333333333)
151
152 #define EQUAL_SIDE_TOL 5e-2 /* tolerance to equate side sizes */
153
154 /* divide rectangle idiv in the list p->rects */
155 static nlopt_result divide_rect(double *rdiv, params *p)
156 {
157      int i;
158      const const int n = p->n;
159      const int L = p->L;
160      double *c = rdiv + 3; /* center of rect to divide */
161      double *w = c + n; /* widths of rect to divide */
162      double wmax = w[0];
163      int imax = 0, nlongest = 0;
164      rb_node *node;
165
166      for (i = 1; i < n; ++i)
167           if (w[i] > wmax)
168                wmax = w[imax = i];
169      for (i = 0; i < n; ++i)
170           if (wmax - w[i] <= wmax * EQUAL_SIDE_TOL)
171                ++nlongest;
172      if (p->which_div == 1 || (p->which_div == 0 && nlongest == n)) {
173           /* trisect all longest sides, in increasing order of the average
174              function value along that direction */
175           double *fv = p->work;
176           int *isort = p->iwork;
177           for (i = 0; i < n; ++i) {
178                if (wmax - w[i] <= wmax * EQUAL_SIDE_TOL) {
179                     double csave = c[i];
180                     c[i] = csave - w[i] * THIRD;
181                     FUNCTION_EVAL(fv[2*i], c, p, 0);
182                     c[i] = csave + w[i] * THIRD;
183                     FUNCTION_EVAL(fv[2*i+1], c, p, 0);
184                     c[i] = csave;
185                }
186                else {
187                     fv[2*i] = fv[2*i+1] = HUGE_VAL;
188                }
189           }
190           sort_fv(n, fv, isort);
191           if (!(node = rb_tree_find(&p->rtree, rdiv)))
192                return NLOPT_FAILURE;
193           for (i = 0; i < nlongest; ++i) {
194                int k;
195                w[isort[i]] *= THIRD;
196                rdiv[0] = rect_diameter(n, w, p);
197                rdiv[2] = p->age++;
198                node = rb_tree_resort(&p->rtree, node);
199                for (k = 0; k <= 1; ++k) {
200                     double *rnew;
201                     ALLOC_RECT(rnew, L);
202                     memcpy(rnew, rdiv, sizeof(double) * L);
203                     rnew[3 + isort[i]] += w[isort[i]] * (2*k-1);
204                     rnew[1] = fv[2*isort[i]+k];
205                     rnew[2] = p->age++;
206                     if (!rb_tree_insert(&p->rtree, rnew)) {
207                          free(rnew);
208                          return NLOPT_OUT_OF_MEMORY;
209                     }
210                }
211           }
212      }
213      else {
214           int k;
215           if (nlongest > 1 && p->which_div == 2) { 
216                /* randomly choose longest side */
217                i = nlopt_iurand(nlongest);
218                for (k = 0; k < n; ++k)
219                     if (wmax - w[k] <= wmax * EQUAL_SIDE_TOL) {
220                          if (!i) { i = k; break; }
221                          --i;
222                     }
223           }
224           else
225                i = imax; /* trisect longest side */
226           if (!(node = rb_tree_find(&p->rtree, rdiv)))
227                return NLOPT_FAILURE;
228           w[i] *= THIRD;
229           rdiv[0] = rect_diameter(n, w, p);
230           rdiv[2] = p->age++;
231           node = rb_tree_resort(&p->rtree, node);
232           for (k = 0; k <= 1; ++k) {
233                double *rnew;
234                ALLOC_RECT(rnew, L);
235                memcpy(rnew, rdiv, sizeof(double) * L);
236                rnew[3 + i] += w[i] * (2*k-1);
237                FUNCTION_EVAL(rnew[1], rnew + 3, p, rnew);
238                rnew[2] = p->age++;
239                if (!rb_tree_insert(&p->rtree, rnew)) {
240                     free(rnew);
241                     return NLOPT_OUT_OF_MEMORY;
242                }
243           }
244      }
245      return NLOPT_SUCCESS;
246 }
247
248 /***************************************************************************/
249 /* Convex hull algorithm, used later to find the potentially optimal
250    points.  What we really have in DIRECT is a "dynamic convex hull"
251    problem, since we are dynamically adding/removing points and
252    updating the hull, but I haven't implemented any of the fancy
253    algorithms for this problem yet. */
254
255 /* Find the lower convex hull of a set of points (x,y) stored in a rb-tree
256    of pointers to {x,y} arrays sorted in lexographic order by (x,y).
257
258    Unlike standard convex hulls, we allow redundant points on the hull,
259    and even allow duplicate points if allow_dups is nonzero.
260
261    The return value is the number of points in the hull, with pointers
262    stored in hull[i] (should be an array of length >= t->N).
263 */
264 static int convex_hull(rb_tree *t, double **hull, int allow_dups)
265 {
266      int nhull = 0;
267      double minslope;
268      double xmin, xmax, yminmin, ymaxmin;
269      rb_node *n, *nmax;
270
271      /* Monotone chain algorithm [Andrew, 1979]. */
272
273      n = rb_tree_min(t);
274      if (!n) return 0;
275      nmax = rb_tree_max(t);
276
277      xmin = n->k[0];
278      yminmin = n->k[1];
279      xmax = nmax->k[0];
280
281      if (allow_dups)
282           do { /* include any duplicate points at (xmin,yminmin) */
283                hull[nhull++] = n->k;
284                n = rb_tree_succ(n);
285           } while (n && n->k[0] == xmin && n->k[1] == yminmin);
286      else
287           hull[nhull++] = n->k;
288
289      if (xmin == xmax) return nhull;
290
291      /* set nmax = min mode with x == xmax */
292 #if 0
293      while (nmax->k[0] == xmax)
294           nmax = rb_tree_pred(nmax); /* non-NULL since xmin != xmax */
295      nmax = rb_tree_succ(nmax);
296 #else
297      /* performance hack (see also below) */
298      {
299           double kshift[2];
300           kshift[0] = xmax * (1 - 1e-13);
301           kshift[1] = -HUGE_VAL;
302           nmax = rb_tree_find_gt(t, kshift); /* non-NULL since xmin != xmax */
303      }
304 #endif
305
306      ymaxmin = nmax->k[1];
307      minslope = (ymaxmin - yminmin) / (xmax - xmin);
308
309      /* set n = first node with x != xmin */
310 #if 0
311      while (n->k[0] == xmin)
312           n = rb_tree_succ(n); /* non-NULL since xmin != xmax */
313 #else
314      /* performance hack (see also below) */
315      {
316           double kshift[2];
317           kshift[0] = xmin * (1 + 1e-13);
318           kshift[1] = -HUGE_VAL;
319           n = rb_tree_find_gt(t, kshift); /* non-NULL since xmin != xmax */
320      }
321 #endif
322
323      for (; n != nmax; n = rb_tree_succ(n)) { 
324           double *k = n->k;
325           if (k[1] > yminmin + (k[0] - xmin) * minslope)
326                continue;
327
328           /* performance hack: most of the points in DIRECT lie along
329              vertical lines at a few x values, and we can exploit this */
330           if (nhull && k[0] == hull[nhull - 1][0]) { /* x == previous x */
331                if (k[1] > hull[nhull - 1][1]) {
332                     double kshift[2];
333                     /* because of the round to float in rect_diameter, above,
334                        it shouldn't be possible for two diameters (x values)
335                        to have a fractional difference < 1e-13.  Note
336                        that k[0] > 0 always in DIRECT */
337                     kshift[0] = k[0] * (1 + 1e-13);
338                     kshift[1] = -HUGE_VAL;
339                     n = rb_tree_pred(rb_tree_find_gt(t, kshift));
340                     continue;
341                }
342                else { /* equal y values, add to hull */
343                     if (allow_dups)
344                          hull[nhull++] = k;
345                     continue;
346                }
347           }
348
349           /* remove points until we are making a "left turn" to k */
350           while (nhull > 1) {
351                double *t1 = hull[nhull - 1], *t2;
352
353                /* because we allow equal points in our hull, we have
354                   to modify the standard convex-hull algorithm slightly:
355                   we need to look backwards in the hull list until we
356                   find a point t2 != t1 */
357                int it2 = nhull - 2;
358                do {
359                     t2 = hull[it2--];
360                } while (it2 >= 0 && t2[0] == t1[0] && t2[1] == t1[1]);
361                if (it2 < 0) break;
362
363                /* cross product (t1-t2) x (k-t2) > 0 for a left turn: */
364                if ((t1[0]-t2[0]) * (k[1]-t2[1])
365                    - (t1[1]-t2[1]) * (k[0]-t2[0]) >= 0)
366                     break;
367                --nhull;
368           }
369           hull[nhull++] = k;
370      }
371
372      if (allow_dups)
373           do { /* include any duplicate points at (xmax,ymaxmin) */
374                hull[nhull++] = nmax->k;
375                nmax = rb_tree_succ(nmax);
376           } while (nmax && nmax->k[0] == xmax && n->k[1] == ymaxmin);
377      else
378           hull[nhull++] = nmax->k;
379
380      return nhull;
381 }
382
383 /***************************************************************************/
384
385 static int small(double *w, params *p)
386 {
387      int i;
388      for (i = 0; i < p->n; ++i)
389           if (w[i] > p->stop->xtol_abs[i] &&
390               w[i] > (p->ub[i] - p->lb[i]) * p->stop->xtol_rel)
391                return 0;
392      return 1;
393 }
394
395 static nlopt_result divide_good_rects(params *p)
396 {
397      const int n = p->n;
398      double **hull;
399      int nhull, i, xtol_reached = 1, divided_some = 0;
400      double magic_eps = p->magic_eps;
401
402      if (p->hull_len < p->rtree.N) {
403           p->hull_len += p->rtree.N;
404           p->hull = (double **) realloc(p->hull, sizeof(double*)*p->hull_len);
405           if (!p->hull) return NLOPT_OUT_OF_MEMORY;
406      }
407      nhull = convex_hull(&p->rtree, hull = p->hull, p->which_opt != 1);
408  divisions:
409      for (i = 0; i < nhull; ++i) {
410           double K1 = -HUGE_VAL, K2 = -HUGE_VAL, K;
411           int im, ip;
412
413           /* find unequal points before (im) and after (ip) to get slope */
414           for (im = i-1; im >= 0 && hull[im][0] == hull[i][0]; --im);
415           for (ip = i+1; ip < nhull && hull[ip][0] == hull[i][0]; ++ip);
416
417           if (im >= 0)
418                K1 = (hull[i][1] - hull[im][1]) / (hull[i][0] - hull[im][0]);
419           if (ip < nhull)
420                K1 = (hull[i][1] - hull[ip][1]) / (hull[i][0] - hull[ip][0]);
421           K = MAX(K1, K2);
422           if (hull[i][1] - K * hull[i][0]
423               <= p->minf - magic_eps * fabs(p->minf) || ip == nhull) {
424                /* "potentially optimal" rectangle, so subdivide */
425                nlopt_result ret = divide_rect(hull[i], p);
426                divided_some = 1;
427                if (ret != NLOPT_SUCCESS) return ret;
428                xtol_reached = xtol_reached && small(hull[i] + 3+n, p);
429           }
430
431           /* for the DIRECT-L variant, we only divide one rectangle out
432              of all points with equal diameter and function values
433              ... note that for p->which_opt == 1, i == ip-1 should be a no-op
434                  anyway, since we set allow_dups=0 in convex_hull above */
435           if (p->which_opt == 1)
436                i = ip - 1; /* skip to next unequal point for next iteration */
437           else if (p->which_opt == 2) /* like DIRECT-L but randomized */
438                i += nlopt_iurand(ip - i); /* possibly do another equal pt */
439      }
440      if (!divided_some) {
441           if (magic_eps != 0) {
442                magic_eps = 0;
443                goto divisions; /* try again */
444           }
445           else { /* WTF? divide largest rectangle with smallest f */
446                /* (note that this code actually gets called from time
447                   to time, and the heuristic here seems to work well,
448                   but I don't recall this situation being discussed in
449                   the references?) */
450                rb_node *max = rb_tree_max(&p->rtree);
451                rb_node *pred = max;
452                double wmax = max->k[0];
453                do { /* note: this loop is O(N) worst-case time */
454                     max = pred;
455                     pred = rb_tree_pred(max);
456                } while (pred && pred->k[0] == wmax);
457                return divide_rect(max->k, p);
458           }
459      }
460      return xtol_reached ? NLOPT_XTOL_REACHED : NLOPT_SUCCESS;
461 }
462
463 /***************************************************************************/
464
465 /* lexographic sort order (d,f,age) of hyper-rects, for red-black tree */
466 int cdirect_hyperrect_compare(double *a, double *b)
467 {
468      if (a[0] < b[0]) return -1;
469      if (a[0] > b[0]) return +1;
470      if (a[1] < b[1]) return -1;
471      if (a[1] > b[1]) return +1;
472      if (a[2] < b[2]) return -1;
473      if (a[2] > b[2]) return +1;
474      return (int) (a - b); /* tie-breaker, shouldn't be needed */
475 }
476
477 /***************************************************************************/
478
479 nlopt_result cdirect_unscaled(int n, nlopt_func f, void *f_data,
480                               const double *lb, const double *ub,
481                               double *x,
482                               double *minf,
483                               nlopt_stopping *stop,
484                               double magic_eps, int which_alg)
485 {
486      params p;
487      int i;
488      double *rnew;
489      nlopt_result ret = NLOPT_OUT_OF_MEMORY;
490
491      p.magic_eps = magic_eps;
492      p.which_diam = which_alg % 3;
493      p.which_div = (which_alg / 3) % 3;
494      p.which_opt = (which_alg / (3*3)) % 3;
495      p.lb = lb; p.ub = ub;
496      p.stop = stop;
497      p.n = n;
498      p.L = 2*n+3;
499      p.f = f;
500      p.f_data = f_data;
501      p.xmin = x;
502      p.minf = HUGE_VAL;
503      p.work = 0;
504      p.iwork = 0;
505      p.hull = 0;
506      p.age = 0;
507
508      rb_tree_init(&p.rtree, cdirect_hyperrect_compare);
509
510      p.work = (double *) malloc(sizeof(double) * (2*n));
511      if (!p.work) goto done;
512      p.iwork = (int *) malloc(sizeof(int) * n);
513      if (!p.iwork) goto done;
514      p.hull_len = 128; /* start with a reasonable number */
515      p.hull = (double **) malloc(sizeof(double *) * p.hull_len);
516      if (!p.hull) goto done;
517
518      if (!(rnew = (double *) malloc(sizeof(double) * p.L))) goto done;
519      for (i = 0; i < n; ++i) {
520           rnew[3+i] = 0.5 * (lb[i] + ub[i]);
521           rnew[3+n+i] = ub[i] - lb[i];
522      }
523      rnew[0] = rect_diameter(n, rnew+3+n, &p);
524      rnew[1] = function_eval(rnew+3, &p);
525      rnew[2] = p.age++;
526      if (!rb_tree_insert(&p.rtree, rnew)) {
527           free(rnew);
528           goto done;
529      }
530
531      ret = divide_rect(rnew, &p);
532      if (ret != NLOPT_SUCCESS) goto done;
533
534      while (1) {
535           double minf0 = p.minf;
536           ret = divide_good_rects(&p);
537           if (ret != NLOPT_SUCCESS) goto done;
538           if (p.minf < minf0 && nlopt_stop_f(p.stop, p.minf, minf0)) {
539                ret = NLOPT_FTOL_REACHED;
540                goto done;
541           }
542      }
543
544  done:
545      rb_tree_destroy_with_keys(&p.rtree);
546      free(p.hull);
547      free(p.iwork);
548      free(p.work);
549               
550      *minf = p.minf;
551      return ret;
552 }
553
554 /* in the conventional DIRECT-type algorithm, we first rescale our
555    coordinates to a unit hypercube ... we do this simply by
556    wrapping cdirect() around cdirect_unscaled(). */
557
558 double cdirect_uf(int n, const double *xu, double *grad, void *d_)
559 {
560      cdirect_uf_data *d = (cdirect_uf_data *) d_;
561      double f;
562      int i;
563      for (i = 0; i < n; ++i)
564           d->x[i] = d->lb[i] + xu[i] * (d->ub[i] - d->lb[i]);
565      f = d->f(n, d->x, grad, d->f_data);
566      if (grad)
567           for (i = 0; i < n; ++i)
568                grad[i] *= d->ub[i] - d->lb[i];
569      return f;
570 }
571
572 nlopt_result cdirect(int n, nlopt_func f, void *f_data,
573                      const double *lb, const double *ub,
574                      double *x,
575                      double *minf,
576                      nlopt_stopping *stop,
577                      double magic_eps, int which_alg)
578 {
579      cdirect_uf_data d;
580      nlopt_result ret;
581      const double *xtol_abs_save;
582      int i;
583
584      d.f = f; d.f_data = f_data; d.lb = lb; d.ub = ub;
585      d.x = (double *) malloc(sizeof(double) * n*4);
586      if (!d.x) return NLOPT_OUT_OF_MEMORY;
587      
588      for (i = 0; i < n; ++i) {
589           x[i] = (x[i] - lb[i]) / (ub[i] - lb[i]);
590           d.x[n+i] = 0;
591           d.x[2*n+i] = 1;
592           d.x[3*n+i] = stop->xtol_abs[i] / (ub[i] - lb[i]);
593      }
594      xtol_abs_save = stop->xtol_abs;
595      stop->xtol_abs = d.x + 3*n;
596      ret = cdirect_unscaled(n, cdirect_uf, &d, d.x+n, d.x+2*n, x, minf, stop,
597                             magic_eps, which_alg);
598      stop->xtol_abs = xtol_abs_save;
599      for (i = 0; i < n; ++i)
600           x[i] = lb[i]+ x[i] * (ub[i] - lb[i]);
601      free(d.x);
602      return ret;
603 }