1 /* Copyright (c) 2007-2008 Massachusetts Institute of Technology
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:
11 * The above copyright notice and this permission notice shall be
12 * included in all copies or substantial portions of the Software.
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.
23 /* Multi-Level Single-Linkage (MLSL) algorithm for
24 global optimization by random local optimizations (a multistart algorithm
25 with "clustering" to avoid repeated detection of the same local minimum),
26 modified to optionally use a Sobol' low-discrepancy sequence (LDS) instead
27 of pseudorandom numbers. See:
29 A. H. G. Rinnooy Kan and G. T. Timmer, "Stochastic global optimization
30 methods," Mathematical Programming, vol. 39, p. 27-78 (1987).
31 [ actually 2 papers -- part I: clustering methods (p. 27), then
32 part II: multilevel methods (p. 57) ]
36 Sergei Kucherenko and Yury Sytsko, "Application of deterministic
37 low-discrepancy sequences in global optimization," Computational
38 Optimization and Applications, vol. 30, p. 297-318 (2005).
40 Compared to the above papers, I made a couple other modifications
41 to the algorithm besides the use of a LDS.
43 1) first, the original algorithm suggests discarding points
44 within a *fixed* distance of the boundaries or of previous
45 local minima; I changed this to a distance that decreases with,
46 iteration, proportional to the same threshold radius used
47 for clustering. (In the case of the boundaries, I make
48 the proportionality constant rather small as well.)
50 2) Kan and Timmer suggest using fancy "spiral-search" algorithms
51 to quickly locate nearest-neighbor points, reducing the
52 overall time for N sample points from O(N^2) to O(N log N)
53 However, recent papers seem to show that such schemes (kd trees,
54 etcetera) become inefficient for high-dimensional spaces (d > 20),
55 and finding a better approach seems to be an open question. Therefore,
56 since I am mainly interested in MLSL for high-dimensional problems
57 (in low dimensions we have DIRECT etc.), I punted on this question
58 for now. In practice, O(N^2) (which does not count the #points
59 evaluated in local searches) does not seem too bad if the objective
60 function is expensive.
71 /* data structure for each random/quasirandom point in the population */
73 double f; /* function value at x */
74 int minimized; /* if we have already minimized starting from x */
75 double closest_pt_d; /* distance^2 to closest pt with smaller f */
76 double closest_lm_d; /* distance^2 to closest lm with smaller f*/
77 double x[1]; /* array of length n (K&R struct hack) */
80 /* all of the data used by the various mlsl routines...it's
81 not clear in hindsight that we need to put all of this in a data
82 structure since most of the work occurs in a single routine,
83 but it doesn't hurt us */
85 int n; /* # dimensions */
86 const double *lb, *ub;
87 nlopt_stopping *stop; /* stopping criteria */
88 nlopt_func f; void *f_data;
90 rb_tree pts; /* tree of points (k == pt), sorted by f */
91 rb_tree lms; /* tree of local minimizers, sorted by function value
92 (k = array of length d+1, [0] = f, [1..d] = x) */
94 nlopt_sobol s; /* sobol data for LDS point generation, or NULL
95 to use pseudo-random numbers */
97 nlopt_algorithm local_alg; /* local search algorithm */
98 int local_maxeval; /* max # local iterations (0 if unlimited) */
100 double R_prefactor, dlm, dbound, gamma; /* parameters of MLSL */
101 int N; /* number of pts to add per iteration */
104 /* comparison routines to sort the red-black trees by function value */
105 static int pt_compare(rb_key p1_, rb_key p2_)
109 if (p1->f < p2->f) return -1;
110 if (p1->f > p2->f) return +1;
113 static int lm_compare(double *k1, double *k2)
115 if (*k1 < *k2) return -1;
116 if (*k1 > *k2) return +1;
120 /* Euclidean distance |x1 - x2|^2 */
121 static double distance2(int n, const double *x1, const double *x2)
125 for (i = 0; i < n; ++i) {
126 double dx = x1[i] - x2[i];
132 /* find the closest pt to p with a smaller function value;
133 this function is called when p is first added to our tree */
134 static void find_closest_pt(int n, rb_tree *pts, pt *p)
136 rb_node *node = rb_tree_find_lt(pts, (rb_key) p);
137 double closest_d = HUGE_VAL;
139 double d = distance2(n, p->x, ((pt *) node->k)->x);
140 if (d < closest_d) closest_d = d;
141 node = rb_tree_pred(node);
143 p->closest_pt_d = closest_d;
146 /* find the closest local minimizer (lm) to p with a smaller function value;
147 this function is called when p is first added to our tree */
148 static void find_closest_lm(int n, rb_tree *lms, pt *p)
150 rb_node *node = rb_tree_find_lt(lms, &p->f);
151 double closest_d = HUGE_VAL;
153 double d = distance2(n, p->x, node->k+1);
154 if (d < closest_d) closest_d = d;
155 node = rb_tree_pred(node);
157 p->closest_lm_d = closest_d;
160 /* given newpt, which presumably has just been added to our
161 tree, update all pts with a greater function value in case
162 newpt is closer to them than their previous closest_pt ...
163 we can ignore already-minimized points since we never do
164 local minimization from the same point twice */
165 static void pts_update_newpt(int n, rb_tree *pts, pt *newpt)
167 rb_node *node = rb_tree_find_gt(pts, (rb_key) newpt);
169 pt *p = (pt *) node->k;
171 double d = distance2(n, newpt->x, p->x);
172 if (d < p->closest_pt_d) p->closest_pt_d = d;
174 node = rb_tree_succ(node);
178 /* given newlm, which presumably has just been added to our
179 tree, update all pts with a greater function value in case
180 newlm is closer to them than their previous closest_lm ...
181 we can ignore already-minimized points since we never do
182 local minimization from the same point twice */
183 static void pts_update_newlm(int n, rb_tree *pts, double *newlm)
188 node = rb_tree_find_gt(pts, (rb_key) &tmp_pt);
190 pt *p = (pt *) node->k;
192 double d = distance2(n, newlm+1, p->x);
193 if (d < p->closest_lm_d) p->closest_lm_d = d;
195 node = rb_tree_succ(node);
199 static int is_potential_minimizer(mlsl_data *mlsl, pt *p,
205 const double *lb = mlsl->lb;
206 const double *ub = mlsl->ub;
207 const double *x = p->x;
212 if (p->closest_pt_d <= dpt_min*dpt_min)
215 if (p->closest_lm_d <= dlm_min*dlm_min)
218 for (i = 0; i < n; ++i)
219 if (x[i] - lb[i] <= dbound_min || ub[i] - x[i] <= dbound_min)
225 #define K2PI (6.2831853071795864769252867665590057683943388)
227 /* compute Gamma(1 + n/2)^{1/n} ... this is a constant factor
228 used in MLSL as part of the minimum-distance cutoff*/
229 static double gam(int n)
231 /* use Stirling's approximation:
232 Gamma(1 + z) ~ sqrt(2*pi*z) * z^z / exp(z)
233 ... this is more than accurate enough for our purposes
234 (error in gam < 15% for d=1, < 4% for d=2, < 2% for d=3, ...)
235 and avoids overflow problems because we can combine it with
236 the ^{1/n} exponent */
238 return sqrt(pow(K2PI * z, 1.0/n) * z) * exp(-0.5);
241 static pt *alloc_pt(int n)
243 pt *p = (pt *) malloc(sizeof(pt) + (n-1) * sizeof(double));
246 p->closest_pt_d = HUGE_VAL;
247 p->closest_lm_d = HUGE_VAL;
252 /* wrapper around objective function that increments evaluation count */
253 static double fcount(int n, const double *x, double *grad, void *p_)
255 mlsl_data *p = (mlsl_data *) p_;
257 return p->f(n, x, grad, p->f_data);
260 static void get_minf(mlsl_data *d, double *minf, double *x)
262 rb_node *node = rb_tree_min(&d->pts);
264 *minf = ((pt *) node->k)->f;
265 memcpy(x, ((pt *) node->k)->x, sizeof(double) * d->n);
267 node = rb_tree_min(&d->lms);
268 if (node && node->k[0] < *minf) {
270 memcpy(x, node->k + 1, sizeof(double) * d->n);
274 #define MIN(a,b) ((a) < (b) ? (a) : (b))
276 #define MLSL_SIGMA 2. /* MLSL sigma parameter, using value from the papers */
277 #define MLSL_GAMMA 0.3 /* MLSL gamma parameter (FIXME: what should it be?) */
279 nlopt_result mlsl_minimize(int n, nlopt_func f, void *f_data,
280 const double *lb, const double *ub, /* bounds */
281 double *x, /* in: initial guess, out: minimizer */
283 nlopt_stopping *stop,
284 nlopt_algorithm local_alg,
286 int Nsamples, /* #samples/iteration (0=default) */
287 int lds) /* random or low-discrepancy seq. (lds) */
289 nlopt_result ret = NLOPT_SUCCESS;
295 d.N = 4; /* FIXME: what is good number of samples per iteration? */
298 if (d.N < 1) return NLOPT_INVALID_ARGS;
301 d.lb = lb; d.ub = ub;
303 d.f = f; d.f_data = f_data;
304 rb_tree_init(&d.pts, pt_compare);
305 rb_tree_init(&d.lms, lm_compare);
306 d.s = lds ? nlopt_sobol_create((unsigned) n) : NULL;
307 d.local_alg = local_alg; d.local_maxeval = local_maxeval;
309 d.gamma = MLSL_GAMMA;
311 d.R_prefactor = sqrt(2./K2PI) * pow(gam(n) * MLSL_SIGMA, 1.0/n);
312 for (i = 0; i < n; ++i)
313 d.R_prefactor *= pow(ub[i] - lb[i], 1.0/n);
315 /* MLSL also suggests setting some minimum distance from points
316 to previous local minimiza and to the boundaries; I don't know
317 how to choose this as a fixed number, so I set it proportional
318 to R; see also the comments at top. dlm and dbound are the
319 proportionality constants. */
320 d.dlm = 1.0; /* min distance/R to local minima (FIXME: good value?) */
321 d.dbound = 1e-6; /* min distance/R to ub/lb boundaries (good value?) */
325 if (!p) { ret = NLOPT_OUT_OF_MEMORY; goto done; }
327 /* FIXME: how many sobol points to skip, if any? */
328 nlopt_sobol_skip(d.s, (unsigned) (10*n+d.N), p->x);
330 memcpy(p->x, x, n * sizeof(double));
331 p->f = f(n, x, NULL, f_data);
333 if (!rb_tree_insert(&d.pts, (rb_key) p)) {
334 free(p); ret = NLOPT_OUT_OF_MEMORY;
336 if (nlopt_stop_evals(stop)) ret = NLOPT_MAXEVAL_REACHED;
337 else if (nlopt_stop_time(stop)) ret = NLOPT_MAXTIME_REACHED;
338 else if (p->f < stop->minf_max) ret = NLOPT_MINF_MAX_REACHED;
340 while (ret == NLOPT_SUCCESS) {
344 get_minf(&d, minf, x);
346 /* sampling phase: add random/quasi-random points */
347 for (i = 0; i < d.N && ret == NLOPT_SUCCESS; ++i) {
349 if (!p) { ret = NLOPT_OUT_OF_MEMORY; goto done; }
350 if (d.s) nlopt_sobol_next(d.s, p->x, lb, ub);
351 else { /* use random points instead of LDS */
353 for (j = 0; j < n; ++j) p->x[j] = nlopt_urand(lb[j],ub[j]);
355 p->f = f(n, p->x, NULL, f_data);
357 if (!rb_tree_insert(&d.pts, (rb_key) p)) {
358 free(p); ret = NLOPT_OUT_OF_MEMORY;
360 if (nlopt_stop_evals(stop)) ret = NLOPT_MAXEVAL_REACHED;
361 else if (nlopt_stop_time(stop)) ret = NLOPT_MAXTIME_REACHED;
362 else if (p->f < stop->minf_max) ret = NLOPT_MINF_MAX_REACHED;
364 find_closest_pt(n, &d.pts, p);
365 find_closest_lm(n, &d.lms, p);
366 pts_update_newpt(n, &d.pts, p);
370 /* distance threshhold parameter R in MLSL */
372 * pow(log((double) d.pts.N) / d.pts.N, 1.0 / n);
374 /* local search phase: do local opt. for promising points */
375 node = rb_tree_min(&d.pts);
376 for (i = (int) (ceil(d.gamma * d.pts.N) + 0.5);
377 node && i > 0 && ret == NLOPT_SUCCESS; --i) {
379 if (is_potential_minimizer(&d, p,
380 R, d.dlm*R, d.dbound*R)) {
383 double t = nlopt_seconds();
385 if (nlopt_stop_evals(stop)) {
386 ret = NLOPT_MAXEVAL_REACHED; break;
388 if (stop->maxtime > 0 &&
389 t - stop->start >= stop->maxtime) {
390 ret = NLOPT_MAXTIME_REACHED; break;
392 lm = (double *) malloc(sizeof(double) * (n+1));
393 if (!lm) { ret = NLOPT_OUT_OF_MEMORY; goto done; }
394 memcpy(lm+1, p->x, sizeof(double) * n);
395 lret = nlopt_minimize(local_alg, n, fcount, &d,
398 stop->ftol_rel, stop->ftol_abs,
399 stop->xtol_rel, stop->xtol_abs,
402 stop->maxeval - stop->nevals)
403 : stop->maxeval - stop->nevals,
404 stop->maxtime - (t - stop->start));
406 if (lret < 0) { free(lm); ret = lret; goto done; }
407 if (!rb_tree_insert(&d.lms, lm)) {
408 free(lm); ret = NLOPT_OUT_OF_MEMORY;
410 else if (*lm < stop->minf_max)
411 ret = NLOPT_MINF_MAX_REACHED;
412 else if (nlopt_stop_evals(stop))
413 ret = NLOPT_MAXEVAL_REACHED;
414 else if (nlopt_stop_time(stop))
415 ret = NLOPT_MAXTIME_REACHED;
417 pts_update_newlm(n, &d.pts, lm);
420 /* TODO: additional stopping criteria based
421 e.g. on improvement in function values, etc? */
423 node = rb_tree_succ(node);
427 get_minf(&d, minf, x);
430 nlopt_sobol_destroy(d.s);
431 rb_tree_destroy_with_keys(&d.lms);
432 rb_tree_destroy_with_keys(&d.pts);