Whoops. Missed off the new script.
[rocl] / graph.c
CommitLineData
6f7ff751 1/* -*-c-*-
2 *
3 * $Id: graph.c,v 1.1 2003/03/07 00:45:13 mdw Exp $
4 *
5 * Graph theory stuff
6 *
7 * (c) 2003 Mark Wooding
8 */
9
10/*----- Licensing notice --------------------------------------------------*
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 */
26
27/*----- Revision history --------------------------------------------------*
28 *
29 * $Log: graph.c,v $
30 * Revision 1.1 2003/03/07 00:45:13 mdw
31 * Graph theory functions.
32 *
33 */
34
35/*----- Header files ------------------------------------------------------*/
36
37#include <assert.h>
38#include <math.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42
43#include <tcl.h>
44
45#include "vec.h"
46
47/*----- Static variables --------------------------------------------------*/
48
49#define INF ((unsigned long)-1)
50
51/*----- Utility functions -------------------------------------------------*/
52
53static int err(Tcl_Interp *ti, /*const*/ char *p)
54{
55 Tcl_SetResult(ti, p, TCL_STATIC);
56 return (TCL_ERROR);
57}
58
59/* --- @import@ --- *
60 *
61 * Arguments: @Tcl_Interp *ti@ = interpreter to leave errors in
62 * @vec *v@ = pointer to input adjacency matrix
63 * @unsigned long *tt@ = pointer to output adjacency matrix
64 * @size_t *nn@ = where to put the table size
65 *
66 * Returns: Tcl return code.
67 *
68 * Use: Imports an adjacency matrix.
69 */
70
71static int import(Tcl_Interp *ti, vec *v, unsigned long **tt, size_t *nn)
72{
73 size_t i;
74 unsigned long *t;
75 size_t n;
76
77 /* --- Check the table is well-formed --- */
78
79 if (v->ndim != 2)
80 return (err(ti, "adjacency matrix must be two-dimensional"));
81 if (v->dim[0].lo != 0 || v->dim[1].lo || v->dim[0].hi != v->dim[1].hi)
82 return (err(ti, "adjacency matrix must be square and zero-origin"));
83 n = *nn = v->dim[0].hi;
84
85 /* --- Copy the data over --- */
86
87 n *= n;
88 assert(n == v->n);
89 t = (void *)Tcl_Alloc(n * sizeof(*t));
90 for (i = 0; i < n; i++) {
91 long l;
92 if (Tcl_GetLongFromObj(ti, v->v[i], &l) != TCL_OK) {
93 Tcl_Free((void *)t);
94 return (TCL_ERROR);
95 }
96 t[i] = l >= 0 ? l : INF;
97 }
98 *tt = t;
99 return (TCL_OK);
100}
101
102/* --- @export@ --- *
103 *
104 * Arguments: @Tcl_Interp *ti@ = interpreter to create output vector
105 * @unsigned long *t@ = pointer to table
106 * @size_t n@ = size of the table
107 *
108 * Returns: A pointer to the vector, or null.
109 *
110 * Use: Exports an adjacency matrix.
111 */
112
113static vec *export(Tcl_Interp *ti, unsigned long *t, size_t n)
114{
115 vec_bound b[2];
116 vec *v;
117 size_t i;
118 Tcl_Obj *o;
119
120 b[0].lo = b[1].lo = 0;
121 b[0].hi = b[1].hi = n;
122 if ((v = vec_create(ti, 2, b, 0)) == 0)
123 return (0);
124 o = Tcl_NewLongObj(-1);
125 Tcl_IncrRefCount(o);
126 for (i = 0; i < v->n; i++) {
127 v->v[i] = t[i] == INF ? o : Tcl_NewLongObj(t[i]);
128 Tcl_IncrRefCount(v->v[i]);
129 }
130 Tcl_DecrRefCount(o);
131 return (v);
132}
133
134/*----- Floyd-Warshall all-points shortest path ---------------------------*/
135
136/* --- @graph-shortest-path VEC@ --- *
137 *
138 * Returns a pair of vectors containing, respectively, the shortest path
139 * length and the successor element in the shortest path. If you say
140 *
141 * destructure {len path} [graph-shortest-path $v]
142 *
143 * then [$len get I J] is the shortest path length from node I to node J, and
144 * [$path get I J] is the first hop on that shortest path. (To compute the
145 * entire path, set K to be that first hop; the next hop is then [$path get K
146 * J], and so on.)
147 *
148 * The adjacency matrix is given in VEC: negative entries indicate no path;
149 * nonnegative entries are weights. All entries must be integers.
150 */
151
152static int cmd_shortestpath(ClientData cd, Tcl_Interp *ti,
153 int objc, Tcl_Obj *const *objv)
154{
155 vec *v, *lv = 0, *pv = 0;
156 size_t n, i, j, k;
157 unsigned long *a = 0, *p = 0;
158 Tcl_Obj *o;
159
160 /* --- Read in the arguments --- */
161
162 if (objc != 2) {
163 err(ti, "usage: graph-shortest-path VEC");
164 goto fail;
165 }
166 if ((v = vec_find(ti, objv[1])) == 0 || import(ti, v, &a, &n) != TCL_OK)
167 goto fail;
168
169 /* --- Set up the path table --- */
170
171 p = (void *)Tcl_Alloc(n * n * sizeof(*p));
172 for (i = 0; i < n; i++) {
173 for (j = 0; j < n; j++)
174 p[i * n + j] = j;
175 p[i * n + i] = INF;
176 }
177
178 /* --- Do the main algorithm --- *
179 *
180 * Not so hard. Just brute force and ignorance.
181 */
182
183 for (k = 0; k < n; k++) {
184 for (i = 0; i < n; i++) {
185 for (j = 0; j < n; j++) {
186 if (a[i * n + k] != INF && a[k * n + j] != INF &&
187 a[i * n + k] + a[k * n + j] < a[i * n + j]) {
188 a[i * n + j] = a[i * n + k] + a[k * n + j];
189 p[i * n + j] = p[i * n + k];
190 }
191 }
192 }
193 }
194
195 /* --- Wrap up --- */
196
197 if ((lv = export(ti, a, n)) == 0 || (pv = export(ti, p, n)) == 0)
198 goto fail;
199 o = Tcl_NewListObj(0, 0);
200 Tcl_ListObjAppendElement
201 (ti, o, Tcl_NewStringObj(Tcl_GetCommandName(ti, lv->c), -1));
202 Tcl_ListObjAppendElement
203 (ti, o, Tcl_NewStringObj(Tcl_GetCommandName(ti, pv->c), -1));
204 Tcl_SetObjResult(ti, o);
205 Tcl_Free((void *)a);
206 Tcl_Free((void *)p);
207 return (TCL_OK);
208
209fail:
210 if (a) Tcl_Free((void *)a);
211 if (p) Tcl_Free((void *)p);
212 if (lv) vec_destroy(ti, lv);
213 if (pv) vec_destroy(ti, pv);
214 return (TCL_ERROR);
215}
216
217/*----- Travelling Salesman Problem ---------------------------------------*/
218
219/* --- @rrange@ --- *
220 *
221 * Arguments: @size_t max@ = maximum number wanted
222 *
223 * Returns: An integer uniformly distributed on %$[0, max)$%.
224 */
225
226static size_t rrange(size_t max)
227{
228 size_t m, z, r;
229
230 z = RAND_MAX/max;
231 m = z * max;
232 do {
233 r = rand();
234 } while (r > m);
235 r /= z;
236 return (r);
237}
238
239/* --- @graph-travelling-salesman [-OPTIONS] ADJ LIST@ --- *
240 *
241 * Solves the Travelling Salesman Problem approximately. Returns a list
242 * containing (firstly) the cost of the computed route, and secondly the
243 * route itself. Only the nodes in LIST are considered. The OPTIONS affect
244 * the algorithm in various ways.
245 *
246 * -cool FACTOR Cooling factor. Default is 1.001. Must be greater
247 * than 1 for the simulated annealing to work.
248 *
249 * -dead COUNT Give up after COUNT cycles with no improvement.
250 * Default is 200.
251 *
252 * -inner COUNT Perform COUNT loops each cooling cycle. Default is
253 * 10000.
254 *
255 * -init TEMP Set the initial temperature to TEMP. Default is not
256 * very helpful. Initial setting should be well above
257 * the maximum cost increase from a cycle.
258 *
259 * -cycle / -nocycle If -cycle is set, solve the classical problem of
260 * finding a minimal cyclic path. If -nocycle is set,
261 * then start at the first node in LIST, and minimize a
262 * tour without caring where the end goes. The default
263 * is -cycle.
264 */
265
266static int cmd_tsp(ClientData cd, Tcl_Interp *ti,
267 int objc, Tcl_Obj *const *objv)
268{
269 /* --- Initial algorithm parameters --- */
270
271 double cool = 1.001;
272 double temp = 1024;
273 long inner = 10000;
274 long dead = 200;
275 int cycle = 1;
276
277 /* --- Other variables --- */
278
279 vec *v;
280 unsigned long *a = 0;
281 size_t n;
282 int nn;
283 size_t *r = 0, *r_best = 0;
284 unsigned long c_best = 0, c_curr, c;
285 size_t i, j, t;
286 long ii, d;
287 int ok;
288 int rc = TCL_ERROR;
289 Tcl_Obj *o, *o2, **oo;
290
291 /* --- Parse the command line --- */
292
293 for (i = 1; i < objc; i++) {
294 int len;
295 char *p = Tcl_GetStringFromObj(objv[i], &len);
296 if (strcmp(p, "-cool") == 0) {
297 i++; if (i >= objc) goto args;
298 if (Tcl_GetDoubleFromObj(ti, objv[i], &cool) != TCL_OK)
299 goto done;
300 if (cool <= 1) {
301 err(ti, "cooling factor must be > 1");
302 goto done;
303 }
304 } else if (strcmp(p, "-init") == 0) {
305 i++; if (i >= objc) goto args;
306 if (Tcl_GetDoubleFromObj(ti, objv[i], &temp) != TCL_OK)
307 goto done;
308 if (temp <= 0) {
309 err(ti, "initial temperature must be > 0");
310 goto done;
311 }
312 } else if (strcmp(p, "-inner") == 0) {
313 i++; if (i >= objc) goto args;
314 if (Tcl_GetLongFromObj(ti, objv[i], &inner) != TCL_OK)
315 goto done;
316 if (inner <= 0) {
317 err(ti, "inner loop count must be > 0");
318 goto done;
319 }
320 } else if (strcmp(p, "-dead") == 0) {
321 i++; if (i >= objc) goto args;
322 if (Tcl_GetLongFromObj(ti, objv[i], &dead) != TCL_OK)
323 goto done;
324 if (dead <= 0) {
325 err(ti, "dead cycles count must be > 0");
326 goto done;
327 }
328 } else if (strcmp(p, "-cycle") == 0)
329 cycle = 1;
330 else if (strcmp(p, "-nocycle") == 0)
331 cycle = 0;
332 else if (strcmp(p, "--") == 0) {
333 i++; break;
334 } else if (*p != '-')
335 break;
336 else {
337 err(ti, "bad option for graph-travelling-salesman");
338 goto done;
339 }
340 }
341
342 /* --- Check the rest --- */
343
344 if (i + 2 != objc) {
345 err(ti, "usage: graph-travelling-salesman [-OPTIONS] ADJ LIST");
346 goto done;
347 }
348 if ((v = vec_find(ti, objv[i])) == 0 || import(ti, v, &a, &n) != TCL_OK)
349 goto done;
350 if (Tcl_ListObjGetElements(ti, objv[i + 1], &nn, &oo) != TCL_OK)
351 goto done;
352 if (!nn)
353 goto wrap;
354
355 r = (void *)Tcl_Alloc(nn * sizeof(*r));
356 r_best = (void *)Tcl_Alloc(nn * sizeof(*r_best));
357 for (i = 0; i < nn; i++) {
358 long l;
359 if (Tcl_GetLongFromObj(ti, oo[i], &l) != TCL_OK)
360 goto done;
361 if (l < 0 || l >= n) {
362 err(ti, "node index out of range");
363 goto done;
364 }
365 r[i] = l;
366 }
367
368 /* --- The one and two node problems are trivial --- *
369 *
370 * Avoiding these prevents us from having to mess with special cases later.
371 */
372
373 if (nn <= 2) {
374 memcpy(r_best, r, nn * sizeof(*r));
375 if (n == 1)
376 c_best = a[r[0] * n + r[0]];
377 else
378 c_best = a[r[0] * n + r[1]];
379 goto wrap;
380 }
381
382 /* --- Randomize the initial vector --- *
383 *
384 * If we're not cycling, then nail the first item in place.
385 */
386
387 for (i = cycle ? 0 : 1; i < nn; i++) {
388 j = rrange(nn - i);
389 t = r[i]; r[i] = r[i + j]; r[i + j] = t;
390 }
391
392 /* --- Compute the initial cost --- *
393 *
394 * If we're not cycling, don't close off at the end. The easiest way to do
395 * that is to start at the end. There are at least three elements.
396 */
397
398 if (cycle) { j = 0; i = nn - 1; }
399 else { j = nn - 1; i = j - 1; }
400 c = 0;
401 for (;;) {
402 c += a[r[i] * n + r[j]];
403 if (!i)
404 break;
405 j = i;
406 i--;
407 }
408
409/* printf("*** initial cost = %lu\n", c_best); */
410 c_curr = c_best = c;
411 memcpy(r_best, r, nn * sizeof(*r));
412
413 /* --- Embark on the main loop --- */
414
415 d = dead;
416 while (d) {
417 ok = 0;
418 for (ii = inner; ii; ii--) {
419 size_t i, j, ilo, ihi, jlo, jhi;
420
421 /* --- Decide on a change to make --- *
422 *
423 * We just swap two nodes around on the path. This is simple and seems
424 * to be effective. Don't allow the first node to be moved if we're
425 * not cycling.
426 */
427
428 if (cycle) {
429 i = rrange(nn);
430 j = rrange(nn);
431 } else {
432 i = rrange(nn - 1) + 1;
433 j = rrange(nn - 1) + 1;
434 }
435
436 /* --- Compute the change in cost --- *
437 *
438 * Since we're only swapping two nodes, we can work out the change
439 * without rescanning the entire path, by just looking at the local
440 * effects.
441 */
442
443 if (i == j)
444 continue; /* No change */
445 if (j < i) { t = i; i = j; j = t; }
446 ilo = (i - 1) % nn; ihi = (i + 1) % nn;
447 jlo = (j - 1) % nn; jhi = (j + 1) % nn;
448
449 c = c_curr;
450 if (j == nn - 1) {
451
452 /* --- This is where the algorithms differ --- *
453 *
454 * If we're producing a cycle, then we need the cost function to wrap
455 * around here. Otherwise, it hits a barrier, and the last node only
456 * has a partial effect.
457 */
458
459 if (cycle) {
460 if (i == 0) {
461 c -= (a[r[jlo] * n + r[j]] +
462 a[r[j] * n + r[i]] +
463 a[r[i] * n + r[ihi]]);
464 c += (a[r[jlo] * n + r[i]] +
465 a[r[i] * n + r[j]] +
466 a[r[j] * n + r[ihi]]);
467 } else goto std;
468 } else {
469 if (i == j - 1) {
470 c -= a[r[ilo] * n + r[i]] + a[r[i] * n + r[j]];
471 c += a[r[ilo] * n + r[j]] + a[r[j] * n + r[i]];
472 } else {
473 c -= (a[r[ilo] * n + r[i]] +
474 a[r[i] * n + r[ihi]] +
475 a[r[jlo] * n + r[j]]);
476 c += (a[r[ilo] * n + r[j]] +
477 a[r[j] * n + r[ihi]] +
478 a[r[jlo] * n + r[i]]);
479 }
480 }
481 } else {
482
483 /* --- Usual case --- *
484 *
485 * This splits into two subcases, depending on whether the areas
486 * overlap.
487 */
488
489 std:
490 if (i == j - 1) {
491 c -= (a[r[ilo] * n + r[i]] +
492 a[r[i] * n + r[j]] +
493 a[r[j] * n + r[jhi]]);
494 c += (a[r[ilo] * n + r[j]] +
495 a[r[j] * n + r[i]] +
496 a[r[i] * n + r[jhi]]);
497 } else {
498 c -= (a[r[ilo] * n + r[i]] +
499 a[r[i] * n + r[ihi]] +
500 a[r[jlo] * n + r[j]] +
501 a[r[j] * n + r[jhi]]);
502 c += (a[r[ilo] * n + r[j]] +
503 a[r[j] * n + r[ihi]] +
504 a[r[jlo] * n + r[i]] +
505 a[r[i] * n + r[jhi]]);
506 }
507 }
508
509 /* --- Decide what to do --- */
510
511 if (c > c_curr &&
512 rrange(65536) >= (size_t)(exp(((double)c_curr -
513 (double)c)/temp) * 65536))
514 continue;
515
516 /* --- Accept the change --- */
517
518 if (c < c_curr)
519 ok = 1;
520 c_curr = c;
521 t = r[i]; r[i] = r[j]; r[j] = t;
522 if (c_curr < c_best) {
523 c_best = c_curr;
524/* printf("*** new best = %lu\n", c_best); */
525 memcpy(r_best, r, nn * sizeof(*r));
526 }
527 }
528 temp /= cool;
529 if (ok)
530 d = dead;
531 else
532 d--;
533 }
534
535 /* --- Done --- */
536
537wrap:
538 o = Tcl_NewListObj(0, 0);
539 o2 = Tcl_NewListObj(0, 0);
540 Tcl_ListObjAppendElement(ti, o, Tcl_NewLongObj(c_best));
541 for (i = 0; i < nn; i++)
542 Tcl_ListObjAppendElement(ti, o2, Tcl_NewLongObj(r_best[i]));
543 Tcl_ListObjAppendElement(ti, o, o2);
544 Tcl_SetObjResult(ti, o);
545 rc = TCL_OK;
546
547 /* --- Tidy up --- */
548
549done:
550 if (a) Tcl_Free((void *)a);
551 if (r) Tcl_Free((void *)r);
552 if (r_best) Tcl_Free((void *)r_best);
553 return (rc);
554
555args:
556 err(ti, "missing argument for option");
557 goto done;
558}
559
560/*----- Initialization ----------------------------------------------------*/
561
562int Graph_SafeInit(Tcl_Interp *ti)
563{
564 static const struct cmd {
565 /*const*/ char *name;
566 Tcl_ObjCmdProc *proc;
567 } cmds[] = {
568 { "graph-shortest-path", cmd_shortestpath },
569 { "graph-travelling-salesman", cmd_tsp },
570 { 0, 0 }
571 };
572
573 const struct cmd *c;
574 if (Tcl_PkgRequire(ti, "vector", "1.0.0", 0) == 0)
575 return (TCL_ERROR);
576 for (c = cmds; c->name; c++)
577 Tcl_CreateObjCommand(ti, c->name, c->proc, 0, 0);
578 if (Tcl_PkgProvide(ti, "graph", "1.0.0"))
579 return (TCL_ERROR);
580 return (TCL_OK);
581}
582
583int Graph_Init(Tcl_Interp *ti)
584{
585 return (Graph_SafeInit(ti));
586}
587
588/*----- That's all, folks -------------------------------------------------*/