]> granicus.if.org Git - graphviz/commitdiff
experimental new dot core
authorarif <devnull@localhost>
Tue, 19 Jan 2010 16:36:09 +0000 (16:36 +0000)
committerarif <devnull@localhost>
Tue, 19 Jan 2010 16:36:09 +0000 (16:36 +0000)
28 files changed:
lib/dotgen2/acyclic.c [new file with mode: 0644]
lib/dotgen2/au_.c [new file with mode: 0644]
lib/dotgen2/au_.h [new file with mode: 0644]
lib/dotgen2/bjm.c [new file with mode: 0644]
lib/dotgen2/bjm0.c [new file with mode: 0644]
lib/dotgen2/ce.c [new file with mode: 0644]
lib/dotgen2/countx.c [new file with mode: 0644]
lib/dotgen2/flat.c [new file with mode: 0644]
lib/dotgen2/level.c [new file with mode: 0644]
lib/dotgen2/main.c [new file with mode: 0644]
lib/dotgen2/minc.c [new file with mode: 0644]
lib/dotgen2/newdot.h [new file with mode: 0644]
lib/dotgen2/ns.c [new file with mode: 0644]
lib/dotgen2/pos.c [new file with mode: 0644]
lib/dotgen2/queue.c [new file with mode: 0644]
lib/dotgen2/radix.c [new file with mode: 0644]
lib/dotgen2/radix.h [new file with mode: 0644]
lib/dotgen2/radix0.c [new file with mode: 0644]
lib/dotgen2/radix1.c [new file with mode: 0644]
lib/dotgen2/save2_minc.c [new file with mode: 0644]
lib/dotgen2/save_minc.c [new file with mode: 0644]
lib/dotgen2/t.c [new file with mode: 0644]
lib/dotgen2/t1.c [new file with mode: 0644]
lib/dotgen2/t2.c [new file with mode: 0644]
lib/dotgen2/timing.c [new file with mode: 0644]
lib/dotgen2/trysort.c [new file with mode: 0644]
lib/dotgen2/xcoord.c [new file with mode: 0644]
lib/dotgen2/xpos.c [new file with mode: 0644]

diff --git a/lib/dotgen2/acyclic.c b/lib/dotgen2/acyclic.c
new file mode 100644 (file)
index 0000000..78dc962
--- /dev/null
@@ -0,0 +1,38 @@
+#include "newdot.h"
+
+static void reverse_edge(graph_t *g, edge_t *e)
+{
+       edge_t  *rev;
+
+       rev = agfindedge(g,e->head,e->tail);
+       if (!rev) rev = agedge(g,e->head,e->tail);
+       merge(rev,ED_minlen(e),ED_weight(e));
+       agdelete(g,e);
+}
+
+static void dfs(graph_t *g, node_t *v)
+{
+    edge_t  *e, *f;
+    node_t  *w;
+                                                                                
+    if (ND_mark(v)) return;
+    ND_mark(v) = TRUE;
+    ND_onstack(v) = TRUE;
+       for (e = agfstout(g,v); e; e = f) {
+               f = agnxtout(g,e);
+        w = e->head;
+        if (ND_onstack(w)) reverse_edge(g,e);
+        else { if (ND_mark(w) == FALSE) dfs(g,w); }
+    }
+    ND_onstack(v) = FALSE;
+}
+
+static void break_cycles(graph_t *g)
+{
+       node_t  *n;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_mark(n) = ND_onstack(n) = FALSE;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               dfs(g,n);
+}
diff --git a/lib/dotgen2/au_.c b/lib/dotgen2/au_.c
new file mode 100644 (file)
index 0000000..7f97eda
--- /dev/null
@@ -0,0 +1,80 @@
+#include "newdot.h"
+#include <math.h>
+
+int mapbool(char *str, int defval)
+{
+       if (str && str[0]) {
+               if (!strcasecmp(str,"false")) return FALSE;
+               if (!strcasecmp(str,"true")) return TRUE;
+               return atoi(str);
+       }
+       return defval;
+}
+
+void *zmalloc(size_t nbytes)
+{
+    char    *rv = malloc(nbytes);
+    if (nbytes == 0) return 0;
+    if (rv == NULL) {fprintf(stderr, "out of memory\n"); abort();}
+    memset(rv,0,nbytes);
+    return rv;
+}
+                                                                                
+void *zrealloc(void *ptr, size_t size, size_t elt, size_t osize)
+{
+    void    *p = realloc(ptr,size*elt);
+    if (p == NULL && size) {fprintf(stderr, "out of memory\n"); abort();}
+    if (osize < size) memset((char*)p+(osize*elt),'\0',(size-osize)*elt);
+    return p;
+}
+                                                                                
+int dot_Verbose;
+char *CmdName = "newdot";
+
+void warn3(char *s0, char *s1, char *s2)
+{
+       fprintf(stderr,"%s: ",CmdName);
+       fprintf(stderr,s0,s1,s2);
+}
+
+int gvround(double arg)
+{
+       return (int)(arg + (arg > 0?.5:-.5));
+}
+
+int gvgetint(void *obj, char *str, int defval)
+{
+       char    *valstr;
+       int             rv;
+       double  frac;
+
+       valstr = agget(obj,str);
+       if (valstr && valstr[0]) {
+               if (sscanf(valstr,"%lf%%",&frac)) rv = gvround(frac * defval);
+               else if (!sscanf(valstr,"%d",&rv)) rv = defval;
+       }
+       else rv = defval;
+       return rv;
+}
+
+Agedge_t *agfindedge(Agraph_t *g, Agnode_t *t, Agnode_t *h)
+{
+       return agedge(g,t,h,0,0);
+}
+
+static void my_init_node(Agraph_t *g, Agobj_t *node, void *arg)
+{ int *sz = arg; agbindrec(node,"level node rec",sz[1],TRUE); }
+static void my_init_edge(Agraph_t *g, Agobj_t *edge, void *arg)
+{ int *sz = arg; agbindrec(edge,"level edge rec",sz[2],TRUE); }
+static void my_init_graph(Agraph_t *g, Agobj_t *graph, void *arg)
+{ int *sz = arg; agbindrec(graph,"level graph rec",sz[0],TRUE); }
+static Agcbdisc_t mydisc = { {my_init_graph,0,0}, {my_init_node,0,0}, {my_init_edge,0,0} };
+
+void agautoinit(Agraph_t *g, int graphinfo_size, int nodeinfo_size, int edgeinfo_size) 
+{
+       int *s;
+
+       s = N_NEW(3,int);       /* until we fix something, this is a storage leak */
+       s[0] = graphinfo_size; s[1] = nodeinfo_size; s[2] = edgeinfo_size;
+       agpushdisc(g,&mydisc,s);
+}
diff --git a/lib/dotgen2/au_.h b/lib/dotgen2/au_.h
new file mode 100644 (file)
index 0000000..2e595cd
--- /dev/null
@@ -0,0 +1,18 @@
+int gvgetint(void *obj, char *str, int defval);
+int gvround(double arg);
+
+extern queue        *new_queue(int);
+extern void            enqueue(queue *, Agnode_t *);
+extern void                    enqueue_neighbors(queue *, Agnode_t *, int);
+extern Agnode_t                *dequeue(queue *);
+extern void                    free_queue(queue *);
+extern void         *zmalloc(size_t);
+
+extern void                    start_timer(void);
+extern double          elapsed_sec(void);
+
+void warn3(char *s0, char *s1, char *s2);
+
+int crossings_below(Agraph_t *g, int northlevel);
+Agedge_t *agfindedge(Agraph_t *g, Agnode_t *t, Agnode_t *h);
+void agautoinit(Agraph_t *g, int, int, int);
diff --git a/lib/dotgen2/bjm.c b/lib/dotgen2/bjm.c
new file mode 100644 (file)
index 0000000..f03df86
--- /dev/null
@@ -0,0 +1,86 @@
+/* Simple and Efficient BiLayer Cross Counting by Barth, Mutzel and Junger */
+typedef struct Agnodeinfo_s {
+       int             order;
+} Agnodeinfo_t;
+typedef struct Agedgeinfo_s {
+       int             useless;
+} Agedgeinfo_t;
+typedef struct Agraphinfo_s {
+       int             useless;
+} Agraphinfo_t;
+#include <cgraph.h>
+#include <radix.h>
+#include <stdlib.h>
+
+#define ND_order(n)            ((n)->u.order)
+
+int crossings(int r, int southsequence[], int q)
+{
+       int     crosscount, firstindex, *tree, treesize;
+       int     t, k, index;
+
+       /* build the accumulator tree */
+       firstindex = 1;
+       while (firstindex<q) firstindex *= 2;
+       treesize = 2*firstindex - 1; /* number of tree nodes */
+       firstindex -= 1; /* index of leftmost leaf */
+       tree = (int *) malloc(treesize*sizeof(int));
+       for (t=0; t<treesize; t++) tree[t] = 0;
+
+       /* count the crossings */
+       crosscount = 0; /* number of crossings */
+       for (k=0; k<r; k++) { /* insert edge k */
+               index = southsequence[k] + firstindex;
+               tree[index]++;
+               while (index > 0) {
+                       if (index%2) crosscount += tree[index+1];
+                       index = (index - 1)/2;
+                       tree[index]++;
+               }
+       }
+       printf("Number of crossings: %d\n",crosscount);
+       free(tree); 
+       return crosscount;
+}
+
+static int edgecmpf(void *arg0, void *arg1)
+{
+       int     major, minor;
+       Agedge_t        *e0, *e1;
+
+       e0 = *(Agedge_t**)arg0;
+       e1 = *(Agedge_t**)arg1;
+       major = ND_order(e0->tail)  - ND_order(e1->tail);
+       if (major) return major;
+       minor = ND_order(e0->head) - ND_order(e1->head);
+       return minor;
+}
+
+
+int main(int argc, char **argv)
+{
+       Agraph_t        *g;
+       Agnode_t        *n;
+       Agedge_t        *e, **edgelist;
+       int             i, ne, p, q, my_order;
+       int             *southseq;
+       char            junk;
+
+       aginit();
+       g = agread(stdin);              /* must be two-layer */
+       i = ne = p = q = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               sscanf(n->name,"%c%d",&junk,&my_order);
+               ND_order(n) = my_order;
+               if (agfstout(g,n)) p++;
+               else q++;
+       }
+       southseq = malloc(agnnodes(g) * sizeof(int));
+       edgelist = malloc(agnedges(g) * sizeof(Agedge_t*));
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               for (e = agfstout(g,n); e; e = agnxtout(g,e)) 
+                       edgelist[ne++] = e;
+       qsort(edgelist,i,sizeof(Agedge_t*),edgecmpf);
+       for (i = 0; i < ne; i++) southseq[i] = ND_order(edgelist[i]->head);
+       crossings(ne, southseq, q);
+}
diff --git a/lib/dotgen2/bjm0.c b/lib/dotgen2/bjm0.c
new file mode 100644 (file)
index 0000000..4bbabaf
--- /dev/null
@@ -0,0 +1,92 @@
+typedef struct Agnodeinfo_s {
+       int             order;
+} Agnodeinfo_t;
+typedef struct Agedgeinfo_s {
+       int             useless;
+} Agedgeinfo_t;
+typedef struct Agraphinfo_s {
+       int             useless;
+} Agraphinfo_t;
+#include <cgraph.h>
+#include <stdlib.h>
+#include <radix.h>
+
+#define ND_order(n)            ((n)->u.order)
+
+int crossings(int r, int southsequence[], int q)
+{
+       int     crosscount, firstindex, *tree, treesize;
+       int     t, k, index;
+
+       /* build the accumulator tree */
+       firstindex = 1;
+       while (firstindex<q) firstindex *= 2;
+       treesize = 2*firstindex - 1; /* number of tree nodes */
+       firstindex -= 1; /* index of leftmost leaf */
+       tree = (int *) malloc(treesize*sizeof(int));
+       for (t=0; t<treesize; t++) tree[t] = 0;
+
+       /* count the crossings */
+       crosscount = 0; /* number of crossings */
+       for (k=0; k<r; k++) { /* insert edge k */
+               index = southsequence[k] + firstindex;
+               tree[index]++;
+               while (index > 0) {
+                       if (index%2) crosscount += tree[index+1];
+                       index = (index - 1)/2;
+                       tree[index]++;
+               }
+       }
+       printf("Number of crossings: %d\n",crosscount);
+       free(tree); 
+       return crosscount;
+}
+
+static int edgecmpf(void *arg0, void *arg1)
+{
+       int     major, minor;
+       Agedge_t        *e0, *e1;
+
+       e0 = *(Agedge_t**)arg0;
+       e1 = *(Agedge_t**)arg1;
+       major = ND_order(e0->tail)  - ND_order(e1->tail);
+       if (major) return major;
+       minor = ND_order(e0->head) - ND_order(e1->head);
+       return minor;
+}
+
+int main(int argc, char **argv)
+{
+       Agraph_t        *g;
+       Agnode_t        *n;
+       Agedge_t        *e;
+       radixrec_t      *edgelist;
+       int             i, ne, p, q, my_order;
+       int             *southseq;
+       char            junk;
+
+       aginit();
+       g = agread(stdin);              /* must be two-layer */
+       i = ne = p = q = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               sscanf(n->name,"%c%d",&junk,&my_order);
+               ND_order(n) = my_order;
+               if (agfstout(g,n)) p++;
+               else q++;
+       }
+       southseq = malloc(agnnodes(g) * sizeof(int));
+       edgelist = malloc(agnedges(g) * sizeof(radixrec_t));
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               for (e = agfstout(g,n); e; e = agnxtout(g,e), ne++) 
+                       { edgelist[ne].key = (ND_order(e->tail) << 16) + ND_order(e->head);
+                         edgelist[ne].data = e; }
+       /*qsort(edgelist,i,sizeof(Agedge_t*),edgecmpf);*/
+       radix_sort(edgelist,ne);
+       for (i = 0; i < ne; i++) printf("%d %d %s %s\n",
+               ((edgelist[i].key & 0xffff0000) >> 16),
+               ((edgelist[i].key & 0x0000ffff) >> 0),
+               ((Agedge_t*)(edgelist[i].data))->tail->name,
+               ((Agedge_t*)(edgelist[i].data))->head->name);
+       for (i = 0; i < ne; i++) southseq[i] = ND_order(((Agedge_t*)(edgelist[i].data))->head);
+       crossings(ne, southseq, q);
+}
diff --git a/lib/dotgen2/ce.c b/lib/dotgen2/ce.c
new file mode 100644 (file)
index 0000000..7e66b51
--- /dev/null
@@ -0,0 +1,16 @@
+static int Base = -1;          /* zero-based arrays */
+#define Odd(n) ((n) % 2)
+static int CountEdges(int Tree[], int n, int last)
+{
+       int             Pos, Sum;
+
+       if (n == last) return 0;
+       Pos = n + Base + 1;
+       Sum = 0;
+       while (Pos >= 1) {
+               if (Odd(Pos))
+                       Sum = Sum + Tree[Pos - 1];
+               Pos = Pos / 2;
+       }
+       return (Tree[1 + Base] - Sum);
+}
diff --git a/lib/dotgen2/countx.c b/lib/dotgen2/countx.c
new file mode 100644 (file)
index 0000000..87808ff
--- /dev/null
@@ -0,0 +1,76 @@
+#include "newdot.h"
+
+/* Simple and Efficient BiLayer Cross Counting by Barth, Mutzel and Junger */
+
+static int count_crossings(rank_t *north, rank_t *south)
+{
+       int     crosscount, firstindex;
+       int             t, k, index;
+       static int trip;
+
+       trip++;
+       /* build the accumulator tree */
+       if (north->tree == 0) {
+               firstindex = 1;
+               while (firstindex < south->n) firstindex *= 2;
+               north->treesize = 2*firstindex - 1; /* number of tree nodes */
+               north->tree = N_NEW(north->treesize,int);
+               firstindex -= 1;
+       }
+       else {
+               firstindex = (north->treesize + 1) / 2 - 1; /* index of leftmost leaf */
+       }
+       for (t=0; t<north->treesize; t++) north->tree[t] = 0;
+
+       /* count the crossings */
+       crosscount = 0; /* number of crossings */
+       for (k=0; k < north->ne; k++) { /* insert edge k */
+               index = ND_order(aghead(((Agedge_t*)(north->edgelist[k].data)))) + firstindex;
+               north->tree[index]++;
+               while (index > 0) {
+                       if (index%2) crosscount += north->tree[index+1];
+                       index = (index - 1)/2;
+                       north->tree[index]++;
+               }
+       }
+       return crosscount;
+}
+
+int crossings_below(Agraph_t *g, int northlevel)
+{
+       int             c;
+       rank_t  *r;
+       node_t  **v;
+       edge_t  *e;
+
+       r = &GD_rank(g)[northlevel];
+       if (r->crossing_cache.valid)
+               return r->crossing_cache.count;
+
+       if (northlevel == GD_maxrank(g)) c = 0; 
+       else {
+               if (!r->edgelist) {     /* set up edgelist */
+                       /* count the edges */
+                       c = 0;
+                       for (v = r->v; *v; v++)
+                               for (e = agfstout(g,*v); e; e = agnxtout(g,e)) c++;
+                       r->edgelist = N_NEW(c+1,radixrec_t);
+                       /* install the edges */
+                       c = 0;
+                       for (v = r->v; *v; v++)
+                               for (e = agfstout(g,*v); e; e = agnxtout(g,e)) {
+                                       r->edgelist[c].key = (ND_order(agtail(e)) << 16) + ND_order(aghead(e));
+                                       r->edgelist[c].data = e;
+                                       c++;
+                               }
+                       r->ne = c;
+               }
+               if (r->ne > 0) {
+                       radix_sort(r->edgelist,r->ne);
+                       c = count_crossings(r,&GD_rank(g)[northlevel+1]);
+               }
+       }
+       r->crossing_cache.count = c;
+       r->crossing_cache.valid = TRUE;
+       return c;
+}
diff --git a/lib/dotgen2/flat.c b/lib/dotgen2/flat.c
new file mode 100644 (file)
index 0000000..e232ba0
--- /dev/null
@@ -0,0 +1,61 @@
+/* this function will have to be modified if we go for
+ * multi-level nodes */
+int is_flat_edge(Agedge_t *e)
+{
+       if (ND_rank(agtail(e)) = ND_rank(aghead(e)) return TRUE;
+       return FALSE;
+}
+
+void model_flat_edge(Agraph_t *flat, Agedge_t *orig)
+{
+       Agnode_t        *u, *v;
+       u = model_flat_node(flat, agtail(orig));
+       v = model_flat_node(flat, aghead(orig));
+       e = agedge(flat,u,v,0,TRUE);    /* could merge edges if multiple? */
+}
+
+Agraph_t *flat_init(Agraph_t *user)
+{
+       flat = agopen("flatgraph",Agstrictdirected,0);
+       for (u = agfstnode(user); u; u = agnxtnode(user,u)) {
+               for (e = agfstout(user,u); e; e = agnxtout(user,e)) {
+                       if (is_flat_edge(e)) 
+                               model_flat_edge(flat,e);
+               }
+       }
+}
+
+void flat_dfs(Agraph_t *flat, Agnode_t *n)
+{
+       ND_mark(n) = TRUE;
+       ND_onstack(n) = TRUE;
+       for (e = agfstout(flat,n); e; e = next_e) {
+               next_e = agnxtout(g,e);
+               if (ND_onstack(aghead(e))) agdelete(flat,e);
+       }
+       ND_onstack(n) = FALSE;
+}
+
+void flat_breakcycles(Agraph_t *flat)
+{
+       for (n = agfstnode(flat); n; n = agnxtnode(flat,n))
+               ND_mark(n) = 0;
+       for (n = agfstnode(flat); n; n = agnxtnode(flat,n))
+               if (!ND_mark(n)) flat_dfs(flat,n);
+}
+
+static void reverse_edge(Agraph_t *g, edge_t *e)
+{
+       edge_t  *rev;
+
+       rev = agfindedge(g,aghead(e),agtail(e));
+       if (!rev) rev = agedge(g,aghead(e),agtail(e),(char*)0,TRUE);
+       merge(rev,ED_minlen(e),ED_weight(e));
+       agdelete(g,e);
+}
+
+void flat_phase(Agraph_t *user)
+{
+       flat = flat_init(user);
+       flat_breakcycles(flat);
+}
diff --git a/lib/dotgen2/level.c b/lib/dotgen2/level.c
new file mode 100644 (file)
index 0000000..5e55e3a
--- /dev/null
@@ -0,0 +1,392 @@
+#include "newdot.h"
+
+static int is_a_cluster(graph_t *g);
+static int is_a_strong_cluster(graph_t *g);
+static void compile_samerank(graph_t *ug, graph_t *parent_clust);
+static int is_internal_to_cluster(edge_t *e);
+static void compile_nodes(graph_t *g, graph_t *Xg);
+static void strong(graph_t *g, node_t *t, node_t *h, edge_t *orig);
+static void weak(graph_t *g, node_t *t, node_t *h, edge_t *orig);
+static void compile_edges(graph_t *ug, graph_t *Xg);
+static void compile_clusters(graph_t *g, graph_t *Xg);
+static void dfs(graph_t *g, node_t *n);
+static void break_cycles(graph_t *g);
+static void readout_levels(graph_t *g, graph_t *Xg);
+static void connect_components(graph_t *g);
+
+void dot_levels(graph_t *g)
+{
+       graph_t         *Xg = agopen("level assignment constraints",Agstrictdirected,0);
+
+       agautoinit(Xg,sizeof(Agraphinfo_t),sizeof(Agnodeinfo_t),sizeof(Agedgeinfo_t));
+       compile_samerank(g,0);
+       compile_nodes(g,Xg);
+       compile_edges(g,Xg);
+       compile_clusters(g,Xg);
+       break_cycles(Xg);
+       connect_components(Xg);
+       rank(Xg,1,MAXINT);
+       readout_levels(g,Xg);
+}
+
+static int is_empty(graph_t *g)
+{
+       return (agfstnode(g) == NILnode);
+}
+
+static int is_a_cluster(graph_t *g)
+{
+       return ((g == g->root) || (!strncasecmp(agnameof(g),"cluster",7)));
+}
+
+static int is_a_strong_cluster(graph_t *g)
+{
+       char    *str;
+       str = agget(g,"compact");
+       return mapbool((str),FALSE);
+}
+
+static int rankset_kind(graph_t *g)
+{
+       char    *str = agget(g,"rank");
+
+       if (str && str[0]) {
+               if (!strcmp(str,"min")) return MINRANK;
+               if (!strcmp(str,"source")) return SOURCERANK;
+               if (!strcmp(str,"max")) return MAXRANK;
+               if (!strcmp(str,"sink")) return SINKRANK;
+               if (!strcmp(str,"same")) return SAMERANK;
+       }
+       return NORANK;
+}
+
+static int is_nonconstraint(edge_t *e)
+{
+       char    *str;
+       str = agget(e,"constraint");
+       return mapbool((str),FALSE);
+}
+
+static node_t *find(node_t *n)
+{
+       node_t          *set;
+       if ((set = ND_set(n))) {
+               if (set != n) set = ND_set(n) = find(set);
+       }
+       else set = ND_set(n) = n;
+       return set;
+}
+
+static node_t *union_one(node_t *leader, node_t *n)
+{
+       if (n) return (ND_set(find(n)) = find(leader));
+       else return leader;
+}
+
+static node_t *union_all(graph_t *g)
+{
+       node_t  *n, *leader;
+
+       n = agfstnode(g);
+       if (!n) return n;
+       leader = find(n);
+       while ((n = agnxtnode(g,n)))
+               union_one(leader,n);
+       return leader;
+}
+
+static void compile_samerank(graph_t *ug, graph_t *parent_clust)
+{
+    graph_t    *s;                     /* subgraph being scanned */
+    graph_t    *clust;         /* cluster that contains the rankset */
+               node_t  *n, *leader;
+
+       if (is_a_cluster(ug)) {
+               clust = ug;
+               GD_parent(ug) = parent_clust;
+               if (parent_clust) GD_level(ug) = GD_level(parent_clust) + 1;
+               else GD_level(ug) = 0;
+       }
+       else clust = parent_clust;
+       if (is_empty(ug)) return;
+
+       /* process subgraphs of this subgraph */
+       for (s = agfstsubg(ug); s; s = agnxtsubg(s))
+               compile_samerank(s,clust);
+
+       /* process this subgraph as a cluster */
+       if (is_a_cluster(ug)) {
+               for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+                       if (ND_cluster(n) == NILgraph) ND_cluster(n) = ug;
+               }
+       }
+
+    /* process this subgraph as a rankset */
+       switch(rankset_kind(ug)) {
+               case SOURCERANK: GD_has_sourcerank(clust) = TRUE; /* fall through */
+               case MINRANK:
+                       leader = union_all(ug);
+                       GD_minrep(clust) = union_one(leader, GD_minrep(clust));
+                       break;
+               case SINKRANK: GD_has_sinkrank(clust) = TRUE; /* fall through */
+               case MAXRANK:
+                       leader = union_all(ug);
+                       GD_maxrep(clust) = union_one(leader, GD_maxrep(clust));
+                       break;
+               case SAMERANK:
+                       leader = union_all(ug);
+                       /* do we need to record these ranksets? */
+                       break;
+               case NORANK:
+                       break;
+               default:        /* unrecognized - warn and do nothing */
+                       warn3("%s has unrecognized rank=%s",agnameof(ug),agget(ug,"rank"));
+       }
+
+       /* a cluster may become degenerate */
+    if (is_a_cluster(ug) && GD_minrep(ug)) {
+               if (GD_minrep(ug) == GD_maxrep(ug)) {
+                       GD_minrep(ug) = GD_maxrep(ug) = union_all(ug);
+               }
+       }
+}
+
+graph_t *dot_lca(graph_t *c0, graph_t *c1)
+{
+       while (c0 != c1) {
+               if (GD_level(c0) >= GD_level(c1))
+                       c0 = GD_parent(c0);
+               else c1 = GD_parent(c1);
+       }
+       return c0;
+}
+
+static int is_internal_to_cluster(edge_t *e)
+{
+       graph_t *par,*ct,*ch;
+       ct = ND_cluster(agtail(e));
+       ch = ND_cluster(aghead(e));
+       if (ct == ch) return TRUE;
+       par = dot_lca(ct,ch);
+       if ((par == ct) || (par == ch)) return TRUE;
+       return FALSE;
+}
+
+static void compile_nodes(graph_t *g, graph_t *Xg)
+{
+       /* build variables */
+       node_t  *n;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               if (find(n) == n) ND_rep(n) = agnode(Xg,agnameof(n),TRUE);
+       }
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               if (ND_rep(n) == NILnode) ND_rep(n) = ND_rep(find(n));
+       }
+}
+
+static void merge(edge_t *e, int minlen, int weight)
+{
+       ED_minlen(e) = MAX(ED_minlen(e),minlen);
+       ED_weight(e) += weight;
+}
+
+static void strong(graph_t *g, node_t *t, node_t *h, edge_t *orig)
+{
+       edge_t  *e;
+       if ((e = agfindedge(g,t,h)) ||
+               (e = agfindedge(g,h,t)) ||
+               (e = agedge(g,t,h,(char*)0,TRUE)))
+           merge(e,ED_minlen(orig),ED_weight(orig));
+       else abort();
+}
+
+static void weak(graph_t *g, node_t *t, node_t *h, edge_t *orig)
+{
+       node_t  *v;
+       edge_t  *e,*f;
+
+       for (e = agfstin(g,t); e; e = agnxtin(g,e)) {
+               /* merge with existing weak edge (e,f) */
+               v = agtail(e);
+               if ((f = agfstout(g,v)) && (aghead(f) == h)) {
+                       return;
+               }
+       }
+       if (!e) {
+               v = agnode(g,0,TRUE);
+               e = agedge(g,v,t,(char*)0,TRUE);
+               f = agedge(g,v,h,(char*)0,TRUE);
+       }
+       ED_minlen(e) = MAX(ED_minlen(e),0);     /* effectively a nop */
+       ED_weight(e) += ED_weight(orig) * BACKWARD_PENALTY;
+       ED_minlen(f) = MAX(ED_minlen(f),ED_minlen(orig));
+       ED_weight(f) += ED_weight(orig);
+}
+
+static void compile_edges(graph_t *ug, graph_t *Xg)
+{
+       node_t  *n;
+       edge_t  *e;
+       node_t  *Xt,*Xh;
+       graph_t *tc,*hc;
+
+       /* build edge constraints */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               Xt = ND_rep(n);
+               for (e = agfstout(ug,n); e; e = agnxtout(ug,e)) {
+                       if (is_nonconstraint(e)) continue;
+                       Xh = ND_rep(find(aghead(e)));
+                       if (Xt == Xh) continue;
+
+                       tc = ND_cluster(agtail(e));
+                       hc = ND_cluster(aghead(e));
+
+                       if (is_internal_to_cluster(e)) {
+                               /* determine if graph requires reversed edge */
+                               if ((find(agtail(e)) == GD_maxrep(ND_cluster(agtail(e))))
+                                || (find(aghead(e)) == GD_minrep(ND_cluster(aghead(e))))) {
+                                       node_t *temp = Xt; Xt = Xh; Xh = temp;
+                               }
+                               strong(Xg,Xt,Xh,e);
+                       }
+                       else {
+                               if (is_a_strong_cluster(tc) || is_a_strong_cluster(hc))
+                                       weak(Xg,Xt,Xh,e);
+                               else
+                                       strong(Xg,Xt,Xh,e);
+                       }
+               }
+       }
+}
+
+static void compile_clusters(graph_t *g, graph_t *Xg)
+{
+       node_t          *n;
+       node_t          *rep, *top = 0, *bot = 0;
+       edge_t          *e;
+       graph_t         *sub;
+
+       if (is_a_cluster(g) && is_a_strong_cluster(g)) {
+               for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+                       if (agfstin(g,n) == NILedge) {
+                               rep = ND_rep(find(n));
+                               if (!top) top = agnode(Xg,"\177top",TRUE);
+                               agedge(Xg,top,rep,(char*)0,TRUE);
+                       }
+                       if (agfstout(g,n) == NILedge) {
+                               rep = ND_rep(find(n));
+                               if (!bot) bot = agnode(Xg,"\177bot",TRUE);
+                               agedge(Xg,rep,bot,(char*)0,TRUE);
+                       }
+               }
+               if (top && bot) {
+                       e = agedge(Xg,top,bot,(char*)0,TRUE);
+                       merge(e,0,STRONG_CLUSTER_WEIGHT);
+               }
+       }
+    for (sub = agfstsubg(g); sub; sub = agnxtsubg(sub))
+               compile_clusters(sub,Xg);
+}
+
+static void reverse_edge(graph_t *g, edge_t *e)
+{
+       edge_t  *rev;
+
+       rev = agfindedge(g,aghead(e),agtail(e));
+       if (!rev) rev = agedge(g,aghead(e),agtail(e),(char*)0,TRUE);
+       merge(rev,ED_minlen(e),ED_weight(e));
+       agdelete(g,e);
+}
+
+static void dfs(graph_t *g, node_t *v)
+{
+    edge_t  *e, *f;
+    node_t  *w;
+                                                                                
+    if (ND_mark(v)) return;
+    ND_mark(v) = TRUE;
+    ND_onstack(v) = TRUE;
+       for (e = agfstout(g,v); e; e = f) {
+               f = agnxtout(g,e);
+        w = aghead(e);
+        if (ND_onstack(w)) reverse_edge(g,e);
+        else { if (ND_mark(w) == FALSE) dfs(g,w); }
+    }
+    ND_onstack(v) = FALSE;
+}
+
+static void break_cycles(graph_t *g)
+{
+       node_t  *n;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_mark(n) = ND_onstack(n) = FALSE;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               dfs(g,n);
+}
+
+static void readout_levels(graph_t *g, graph_t *Xg)
+{
+       node_t  *n;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               ND_rank(n) = ND_rank(ND_rep(find(n)));
+       }
+}
+
+static void dfscc(graph_t *g, node_t *n, int cc)
+{
+       edge_t  *e;
+       if (ND_mark(n) == 0) {
+               ND_mark(n) = cc;
+               for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                       dfscc(g,aghead(e),cc);
+               for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                       dfscc(g,agtail(e),cc);
+       }
+}
+
+static void connect_components(graph_t *g)
+{
+       int             cc = 1;
+       node_t  *n;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_mark(n) = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               if (ND_mark(n) == 0) dfscc(g,n,cc++);
+       if (cc > 1) {
+               node_t  *root = agnode(g,"\177root",TRUE);
+               int             ncc = 1;
+               for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+                       if (ND_mark(n) == ncc) {
+                               (void) agedge(g,root,n,(char*)0,TRUE);
+                               ncc++;
+                       }
+               }
+       }
+}
+
+static void aaa(graph_t *g)
+{
+       node_t  *n;
+       edge_t  *e;
+       Agsym_t *weight, *minlen, *label;
+       char    buf[64];
+
+       weight = agattr(g,AGEDGE,"weight","");
+       minlen = agattr(g,AGEDGE,"minlen","");
+       label = agattr(g,AGEDGE,"label","");
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
+                       sprintf(buf,"%f",ED_weight(e));
+                       agxset(e,weight,buf);
+                       sprintf(buf,"%f",ED_minlen(e));
+                       agxset(e,minlen,buf);
+                       sprintf(buf,"%.3f,%.3f",ED_weight(e),ED_minlen(e));
+                       agxset(e,label,buf);
+               }
+       }
+}
+void printgraph(Agraph_t *g) {
+       aaa(g);
+       agwrite(g,stderr);
+}
diff --git a/lib/dotgen2/main.c b/lib/dotgen2/main.c
new file mode 100644 (file)
index 0000000..b36d0be
--- /dev/null
@@ -0,0 +1,56 @@
+#include "newdot.h"
+
+void readin_attrs(graph_t *g)
+{
+       node_t  *n;
+       edge_t  *e;
+       Agsym_t *ap_weight, *ap_minlen;
+
+       ap_weight = agattr(g,AGEDGE,"weight","1");
+       ap_minlen = agattr(g,AGEDGE,"minlen","1");
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
+                       ED_weight(e) =atoi(agxget(e,ap_weight));
+                       ED_minlen(e) = atoi(agxget(e,ap_minlen));
+               }
+       }
+}
+
+void attach_attributes(graph_t *g)
+{
+       node_t  *n;
+       Agsym_t *rank, *order;
+       char    buf[64];
+
+       rank = agattr(g,AGNODE,"rank","");
+       order = agattr(g,AGNODE,"order","");
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               sprintf(buf,"%d",ND_rank(n));
+               agxset(n,rank,buf);
+               sprintf(buf,"%d",ND_order(n));
+               agxset(n,order,buf);
+       }
+}
+
+void init_graph(graph_t *g)
+{
+       aginit(g,AGRAPH,"graphviz",sizeof(Agraphinfo_t),TRUE);
+       aginit(g,AGNODE,"graphviz",sizeof(Agnodeinfo_t),TRUE);
+       aginit(g,AGEDGE,"graphviz",sizeof(Agedgeinfo_t),TRUE);
+}
+
+int main(int argc, char *argv[])
+{
+       graph_t *g;
+       FILE            *in;
+       if (argc > 1) in = fopen(argv[1],"r");
+       else in = stdin;
+       g = agread(in,0);
+       init_graph(g);
+       readin_attrs(g);
+       dot_levels(g);
+//     if (argc <= 2) dot_mincross(g);
+       attach_attributes(g);
+       agwrite(g,stdout);
+       return 1;
+}
diff --git a/lib/dotgen2/minc.c b/lib/dotgen2/minc.c
new file mode 100644 (file)
index 0000000..cdf83e6
--- /dev/null
@@ -0,0 +1,1334 @@
+#include "newdot.h"
+
+/* given:
+ *             ND_rank(v)                      int                     integer level assignments
+ *             ND_ranksize(v)  int                     number of levels (assumed >= 1)
+ *     ED_xpenalty(e)  float   crossing penalty factor
+ * find:
+ *             ND_order(v)                     int                     index within rank
+ *             ND_pos(v)                               coord           (define: is this the center point?)
+ *             ED_pos(e)                               coord*  (define: polyline?)
+ *
+ *             todo: node, edge, graph labels, flat edges
+ */
+
+/* internal graph variables:
+ * user graph
+ *       ND_cluster(v)         graph*  lowest containing cluster
+ *             GD_model(g)                     graph*  model graph
+ *             GD_parent(g)            graph*  parent cluster
+ *             GD_level                                int                     distance from layout root
+ *
+ * model graph objects
+ *             GD_usergraph(g) graph*          original graph or one of its clusters
+ *             GD_minrank(g)           int                             lowest rank index incl. external edges
+ *             GD_maxrank(g)           int                             highest rank index incl. external edges
+ *             GD_minlocalrank(g)              int     lowest rank index of internal nodes
+ *             GD_maxlocalrank(g)              int     highest rank index of internal nodes
+ *             GD_level                                int                             distance from layout root
+ *             GD_rank(g)                      rank_t[]
+ *             GD_repdict(g)           Dict_t*         local representatives of user objects
+ *             ND_component(v) int                             connected component number
+ *             ND_order(v)                     int                             order within rank
+ *             ND_type(v)                      int
+ *                     NODE, TALLNODE  primitive node
+ *                     EXTNODE represents endpoint of an external cluster edge
+ *                     SKELETON represents an internal cluster
+ *                     PATH    for virtual nodes in edges of all kinds
+ *             ND_cluster(v)           graph*          for EXTNODE, its lowest user cluster
+                                                                                                                         for SKELETON, its user cluster
+                                                                                                                               for PATH, NODE, TALLNODE  0?
+ *             ND_sortweight(v)                        for mincross and presort before mincross
+ *             ND_sortweight_defined(v)        (sortweight undefined if up/down degree==0)
+ *             ND_isglobal(v)                                          marks objects for the global merged graph
+ */
+
+/* TODO
+       where do we use port.defined?
+ */
+
+/* forward declarations */
+static void transpose_sweep(Agraph_t* g, int reverse);
+static void mincross_sweep(Agraph_t* g, int dir, boolean reverse);
+static void restorebest(graph_t *g);
+static Agraph_t *globalize(Agraph_t *user);
+static int crossings(graph_t *g);
+static void rec_cluster_init(Agraph_t *ug);
+static int left2right(Agraph_t *g, node_t *v, node_t *w);
+static int in_cross(node_t *v,node_t *w);
+static int out_cross(node_t *v, node_t *w);
+static boolean medians(Agraph_t *g, int r0, int r1);
+static void reorder(graph_t *g, int r, boolean reverse, boolean hasfixed);
+static void savebest(graph_t *g);
+static Agnode_t *choosenode(Agnode_t *fst, Agnode_t *snd);
+static void cluster_ranksetup(Agraph_t *user);
+static void cluster_extents(Agraph_t *g);
+static void cluster_rankstorage(Agraph_t *g);
+static Agnode_t *bindnode(Agraph_t *model, Agnode_t *orignode);
+
+
+/*** basic user<->model association maps ***/
+/* determine canonical order of n0, n1 */
+static void getlowhigh(Agnode_t **n0, Agnode_t **n1)
+{
+       Agnode_t        *temp;
+       int                             d;
+       d =  ND_rank(*n1) - ND_rank(*n0);
+       if ((d < 0) || ((d == 0) && (AGID(*n1)< AGID(*n0))))
+               {temp = *n0; *n0 = *n1; *n1 = temp;}
+}
+
+static int inrange(int a, int b, int c)
+{
+       return ((a <= b) && (b <= c));
+}
+
+static int repkeycmp(Dt_t *d, void *arg0, void *arg1, Dtdisc_t *disc)
+{
+       void *key0 = ((repkey_t*)arg0)->key;
+       void *key1 = ((repkey_t*)arg1)->key;
+       int             rv;
+#ifdef NOTDEF
+       /* not sure why I coded this - SCN   12/27/2005 */
+       switch(agobjkind(key0)) {
+       case AGNODE:
+               rv = AGID(*(Agnode_t*)key1) - AGID(*(Agnode_t*)key0);
+               break;
+       case AGEDGE:
+               rv = AGID(*(Agedge_t*)key1) - AGID(*(Agedge_t*)key0);
+               break;
+       case AGRAPH:
+               /* in libgraph we don't seem to have graph ids, so use pointer */
+               rv = (unsigned long)key1 - (unsigned long) key0;
+               break;
+       default:
+               rv = 0;
+       }
+#else
+       rv = (int)((unsigned long)key1 - (unsigned long) key0);
+#endif
+       return rv;
+}
+
+static Dtdisc_t Repdisc = {
+       0,                      /* pass whole object as key */
+       0,                      /* key size and type */
+       -1,                     /* link offset */
+       (Dtmake_f)0,
+       (Dtfree_f)0,
+       (Dtcompar_f) repkeycmp,
+       (Dthash_f)0,
+       (Dtmemory_f)0,
+       (Dtevent_f)0
+};
+
+static Dict_t *repdict(Agraph_t *model)
+{
+       Dict_t  *dict;
+
+       dict = GD_repdict(model);
+       if (!dict) dict = GD_repdict(model) = dtopen(&Repdisc,Dttree);
+       return dict;
+}
+
+static rep_t association(Agraph_t *model, void *obj)
+{
+       Dict_t *dict;
+       repkey_t        key, rv, *p;
+
+       dict = repdict(model);
+       key.key = obj;          /* i hate when other people code like this */
+       if ((p = dtsearch(dict,&key))) rv = *p;
+       else {rv.val.type = 0; rv.val.p = 0;}
+       return rv.val;
+}
+
+static void associate(Agraph_t *model, void *key, rep_t val)
+{
+       Dict_t          *dict;
+       repkey_t        *newelt;
+
+       assert(association(model,key).type == 0);
+       dict = repdict(model);
+       newelt = NEW(repkey_t);
+       newelt->key = key;
+       newelt->val = val;
+       dtinsert(dict,newelt);
+       if (association(model,key).type == 0) abort();
+}
+
+/* given a value, find (some) rep (including the key) that maps to it. 
+ * for now we'll brute force it.  note that val may always be an external
+ * edge path, so we wouldn't necessarily need the full inverse map.
+ */
+static repkey_t invassociation(Agraph_t *model, void *val)
+{
+       Dict_t  *dict;
+       repkey_t *p;
+       dict = repdict(model);
+       for (p = dtfirst(dict); p; p = dtnext(dict,p)) {
+               if (p->val.p == val) break;
+       }
+       assert(p);
+       return *p;
+}
+
+typedef struct component_s {
+       int             n;
+       node_t  **root;
+       int r;
+} component_t;
+
+
+
+/* from level.c - eventually clean this up. */
+static int is_a_cluster(Agraph_t *g)
+{
+       return ((g == g->root) || (!strncasecmp(agnameof(g),"cluster",7)));
+}
+
+/* find the cluster of n that is an immediate child of g */
+static Agraph_t *subclustof(Agnode_t *n, Agraph_t *g)
+{
+       Agraph_t                *rv;
+       for (rv = ND_cluster(n); rv && (GD_parent(rv) != g); rv = GD_parent(rv));
+       return rv;
+}
+
+static void *T_array(int low, int high, int size)
+{
+       char    *rv;
+
+       rv = calloc((high - low + 1),size);
+       rv = rv - (low * size);
+       return rv;
+}
+
+static void *T_base(void *array, int low, int size)
+{
+       char    *rv;
+       rv = array;
+       rv = rv + low * size;
+       return rv;
+}
+
+static void freearray(void *array, int low, int size)
+{
+       free(T_base(array,low,size));
+}
+
+static Agnode_t **nodearray(int low, int high)
+{ return (Agnode_t**) T_array(low,high,sizeof(Agnode_t*)); }
+
+static Agedge_t **edgearray(int low, int high)
+{ return (Agedge_t**) T_array(low,high,sizeof(Agedge_t*)); }
+
+static int *intarray(int low, int high)
+{ return (int*) T_array(low,high,sizeof(int)); }
+
+/* internal functions for model graph construction and maintenance */
+static vpath_t *newpath(Agraph_t *model, Agnode_t *u, int low, Agnode_t *v, int high)
+{
+       vpath_t         *path;
+       int                             i;
+       path = NEW(vpath_t);
+       path->low = low; path->high = high;
+       path->v = nodearray(low, high);
+       path->e = edgearray(low, high);
+       for (i = low; i <= high; i++) {
+               if ((i == low) && u) path->v[i] = u;
+               else if ((i == high) && v) path->v[i] = v;
+               else {
+                       path->v[i] = agnode(model,0,TRUE);
+                       ND_rank(path->v[i]) = i;
+               }
+               if (i > low) path->e[i-1] = agedge(model,path->v[i-1],path->v[i],0,TRUE);
+       }
+       return path;
+}
+
+static void attributepath(vpath_t *path, int nodetype, Agraph_t *clust)
+{
+       int             i;
+       for (i = path->low; i <= path->high; i++) {
+               ND_type(path->v[i]) = nodetype;
+               ND_cluster(path->v[i]) = clust;
+       }
+}
+
+static rep_t model_edge(Agraph_t *model, Agedge_t *orig)
+{
+       Agedge_t        *e;
+       Agnode_t        *low, *high, *u, *v;
+       port_t          lowport, highport;
+       vpath_t         *path;
+       rep_t                   rep;
+       int                             i;
+
+       rep = association(model,orig);
+       if (rep.type == 0) {
+               low = agtail(orig); high = aghead(orig);
+               getlowhigh(&low,&high);
+               u = bindnode(model,low);
+               assert(u);
+               v = bindnode(model,high);
+               assert(v);
+               path = newpath(model,u,ND_rank(low),v,ND_rank(high));
+               rep.type = PATH;
+               rep.p = path;
+               associate(model,orig,rep);
+       }
+       else path = rep.p;
+
+       /* merge the attributes of orig */
+       for (i = path->low; i < path->high; i++) {
+               e = path->e[i];
+               ED_xpenalty(e) += ED_xpenalty(orig);
+               ED_weight(e) += ED_weight(orig);
+       }
+
+       /* deal with ports.  note that ends could be swapped. */
+       if (ND_rank(agtail(orig)) <= ND_rank(aghead(orig))) {
+               lowport = ED_tailport(orig);
+               highport = ED_headport(orig);
+       }
+       else {
+               highport = ED_tailport(orig);
+               lowport = ED_headport(orig);
+       }
+       if (lowport.defined)
+               path->avgtailport = ((path->weight * path->avgtailport) + ED_weight(orig) * lowport.p.x) / (path->weight + ED_weight(orig));
+       if (highport.defined)
+               path->avgheadport = ((path->weight * path->avgheadport) + ED_weight(orig) * highport.p.x) / (path->weight + ED_weight(orig));
+       path->weight += ED_weight(orig);
+       return rep;
+}
+
+/* bind/construct representative of an internal node in a model graph */
+static rep_t model_intnode(Agraph_t *model, Agnode_t *orig)
+{
+       int                             nr;
+       rep_t                   rep;
+       Agnode_t        *v;
+       
+       rep = association(model,orig);
+       if (rep.type) return rep;
+       
+       nr = ND_ranksize(orig);
+       if (nr <= 1) {                  /* simple case */
+               rep.type = NODE;
+               v = rep.p = agnode(model,agnameof(orig),TRUE);
+               ND_rank(v) = ND_rank(orig);
+       }
+       else {                                                  /* multi-rank node case */
+               rep.type = TALLNODE;
+               rep.p = newpath(model,NILnode,ND_rank(orig),NILnode,ND_rank(orig)+nr-1);
+               attributepath(rep.p,TALLNODE,0);
+       }
+       associate(model,orig,rep);
+       return rep;
+}
+
+/* bind/construct representative of an external endpoint to a model graph */
+static rep_t model_extnode(Agraph_t *model, Agnode_t *orig)
+{
+       Agnode_t        *v;
+       rep_t           rep;
+
+       rep = association(model,orig);
+       if (rep.type) return rep;
+
+       /* assume endpoint is represented by one node, even if orig is multi-rank.
+        * also we aren't keeping track of ports yet */
+       rep.p = v = agnode(model,agnameof(orig),TRUE);
+       rep.type = EXTNODE;
+       ND_rank(v) = ND_rank(orig);     /* should be ND_rank(orig)+ranksize(orig)? */
+       associate(model,orig,rep);
+       return rep;
+}
+
+/* bind/construct representative of an internal cluster of a model graph */
+static rep_t model_clust(Agraph_t *model, Agraph_t *origclust)
+{
+       rep_t           rep;
+       vpath_t *path;
+
+       rep = association(model,origclust);
+       if (rep.type) return rep;
+
+       rep.p = path = newpath(model,NILnode,GD_minrank(origclust),NILnode,GD_maxrank(origclust));
+       rep.type = SKELETON;
+       associate(model,origclust,rep);
+       return rep;
+}
+
+/* helper functions for model_edge */
+static rep_t rep_of_node(Agraph_t *model, Agnode_t *orignode)
+{
+       rep_t   rep;
+       if (agcontains(GD_usergraph(model),orignode))
+               rep = model_intnode(model,orignode);
+       else
+               rep = model_extnode(model,orignode);
+       return rep;
+}
+
+static Agnode_t *bindnode(Agraph_t *model, Agnode_t *orignode)
+{
+       rep_t   rep;
+       rep = rep_of_node(model,orignode);
+       switch (rep.type) {
+       case NODE: case EXTNODE: return (Agnode_t*)(rep.p);
+       default: return ((vpath_t*)(rep.p))->v[ND_rank(orignode)];
+       }
+}
+
+static int leftmost(Agraph_t *model, int r) { return 0; }
+static int rightmost(Agraph_t *model, int r) {return GD_rank(model)[r].n - 1;}
+
+static void flat_edges(Agraph_t *clust)
+{
+#ifdef NOTDEF
+       for (n = agfstnode(clust); n; n = agnxtnode(clust)) {
+               for (e = agfstedge(root,n); e; e = agnxtedge(root,e,n)) {
+               }
+       }
+       ordered_edges();
+#endif
+}
+
+static void search_component(Agraph_t *g, Agnode_t *n, int c)
+{
+       Agedge_t        *e;
+       ND_component(n) = c;
+       for (e = agfstout(g,n); e; e = agnxtout(g,e))
+               if (ND_component(aghead(e)) < 0)
+                       search_component(g,aghead(e),c);
+       for (e = agfstin(g,n); e; e = agnxtin(g,e))
+               if (ND_component(agtail(e)) < 0)
+                       search_component(g,agtail(e),c);
+}
+
+static int ND_comp_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_component(*(Agnode_t**)arg1) - ND_component(*(Agnode_t**)arg0);
+}
+
+static component_t build_components(Agraph_t *g, boolean down)
+{
+       component_t     rv;
+       node_t  *n;
+       int             r, rootcnt, compcnt, deg;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_component(n) = -1;   /* initialize to unknown component */
+
+       compcnt = 0; rootcnt = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) { 
+               /* set priority for subsequent BFS to install nodes, and record roots */
+               if (down) deg = ND_indeg(n);
+               else deg = ND_outdeg(n);
+               ND_priority(n) = deg;
+               if (deg == 0) rootcnt++;
+               /* count and mark components */
+               if (ND_component(n) < 0)
+                       search_component(g,n,compcnt++);
+       }
+
+       rv.n = compcnt;
+       rv.r = rootcnt;
+       rv.root = N_NEW(rv.r,Agnode_t*);
+       r = 0;
+       /* install roots in root list */
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               if (ND_priority(n) == 0) rv.root[r++] = n;
+       /* sort root list so components are contiguous */
+       qsort(rv.root,rv.n,sizeof(node_t*),ND_comp_cmpf);
+       return rv;
+}
+
+static void install(Agraph_t *g, Agnode_t *n)
+{
+       int                             rank;
+       rank_t          *r;
+
+       rank = ND_rank(n);
+       r = &GD_rank(g)[rank];
+       r->v[r->n] = n;
+       ND_order(n) = r->n++;
+}
+
+/* 
+ populates rank lists of g.  there are some key details:
+ 1) the input graph ordering must be respected (in left to right initialization)
+ 2) connected components are separated and marked with indices
+ 3) series-parallel graphs (includes trees, obviously) must not have crossings
+*/
+static void build_ranks(Agraph_t *ug, boolean down)
+{
+       queue                   *q;
+       component_t c;
+       int                             r;
+       Agnode_t        *n;
+       Agedge_t        *e;
+       Agraph_t        *g;
+
+       g = GD_model(ug);
+       c = build_components(g, down);
+
+       /* process each each component */
+       q = new_queue(agnnodes(g)+1);
+       for (r = 0; r < c.r; r++) {
+               enqueue(q,c.root[r]);
+               if ((r + 1 >= c.r)||(ND_component(c.root[r])!=ND_component(c.root[r+1]))) {
+                       while ((n = dequeue(q))) {
+                               install(g,n);
+                                       if (down) {
+                                               for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                                                       if (--ND_priority(aghead(e)) == 0) enqueue(q,aghead(e));
+                                       }
+                                       else {
+                                               for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                                                       if (--ND_priority(agtail(e)) == 0) enqueue(q,aghead(e));
+                                       }
+                       }
+               }
+       }
+       free_queue(q);
+}
+
+/* this predicate indicates if mincross should be run on this cluster */
+static boolean run(Agraph_t *mg)
+{
+       if (GD_pass(mg) > GD_maxpass(mg)) return FALSE;
+       if (GD_pass(mg) - GD_lastwin(mg) > GD_mintry(mg)) return FALSE;
+       GD_pass(mg) = GD_pass(mg) + 1;
+       return TRUE;
+}
+
+static void set_presortkey(Agnode_t *n)
+{
+       int                     key;
+       vpath_t *skel;
+       Agraph_t *model, *parmodel;
+       Agnode_t        *orignode, *parnode;
+
+       model = agraphof(n);
+       parmodel = GD_model(GD_parent(GD_usergraph(model)));
+       skel = association(parmodel,GD_usergraph(model)).p;
+       switch(ND_type(n)) {
+       case EXTNODE:
+               orignode = association(model,n).p;
+               parnode = association(parmodel,orignode).p;
+               if ((skel->low <= ND_rank(n)) && (ND_rank(n) <= skel->high)) {
+                       int             r = ND_rank(n);
+                       if (ND_order(parnode) < ND_order(skel->v[r]))
+                               key = ND_order(GD_rank(model)[r].v[leftmost(model,ND_rank(n))]) -
+                                       (ND_order(skel->v[ND_rank(n)]) - ND_order(parnode));
+                       break;
+               }
+               else {
+                       key = ND_order(parnode);
+               }
+               break;
+       default:
+                       key = ND_order(n);
+                       break;
+       }
+       ND_sortweight(n) = key;
+       ND_sortweight_defined(n) = TRUE;
+}
+
+/* helper function */
+static int presort_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_sortweight(*(Agnode_t**)arg1) - ND_sortweight(*(Agnode_t**)arg0);
+}
+
+/*
+ * sort the nodes of a subgraph so EXTNODEs are adjusted
+ * according to the parent cluster before the main mincross
+ * phase runs.
+ */
+static void presort(Agraph_t *ug)
+{
+       int             r;
+       int             i;
+       Agraph_t        *mg;
+
+       if (ug == ug->root) return;
+       mg = GD_model(ug);
+       for (r = GD_minrank(mg); r <= GD_maxrank(mg); r++) {
+               for (i = leftmost(mg,r); i < rightmost(mg,r); i++)
+                       set_presortkey(GD_rank(mg)[r].v[i]);
+               qsort(GD_rank(mg)[r].v,GD_rank(mg)[r].n,sizeof(Agnode_t*),presort_cmpf);
+               for (i = leftmost(mg,r); i < rightmost(mg,r); i++)
+                       ND_order(GD_rank(mg)[r].v[i]) = i;
+       }
+}
+
+/* sets ports that represent connections to subclusters */
+static void subclustports(Agraph_t *ug)
+{
+       Agraph_t        *model, *clustmodel;
+       Agnode_t        *x;
+       Agedge_t        *e;
+       vpath_t         *p;
+       repkey_t        *ent;
+       Dict_t          *d;
+       double          frac;
+
+       /* walk all the paths */
+       model = GD_model(ug);
+       d = repdict(model);
+       for (ent = dtfirst(d); ent; ent = dtnext(d,ent)) {
+               if (ent->val.type != PATH) continue;
+               p = ent->val.p;
+               if ((ND_type(p->v[p->low])) == SKELETON) {
+                       x = p->v[p->low];
+                       clustmodel = GD_model(ND_cluster(x));
+                       frac = (ND_order(x) + 1) / GD_rank(clustmodel)[ND_rank(x)].n;
+                       e = p->e[p->low];
+                       ED_tailport(e).p.x = 2 * PORTRANGE * (frac - 0.5) + p->avgtailport;
+               }
+               if ((ND_type(p->v[p->high])) == SKELETON) {
+                       x = p->v[p->high];
+                       clustmodel = GD_model(ND_cluster(x));
+                       frac = (ND_order(x) + 1) / GD_rank(clustmodel)[ND_rank(x)].n;
+                       e = p->e[p->high-1];
+                       ED_headport(e).p.x = 2 * PORTRANGE * (frac - 0.5) + p->avgheadport;
+               }
+       }
+}
+
+static void mincross_clust(Agraph_t *ug)
+{
+       Agraph_t        *g;
+       g = GD_model(ug);
+       if (run(g)) {
+               presort(ug);            /* move the external nodes */
+               subclustports(ug);
+               do {
+                       mincross_sweep(g,GD_pass(g)%2,GD_pass(g)%4<2);
+               } while (run(g));
+               transpose_sweep(g,TRUE);
+               restorebest(g);
+       }
+}
+
+static void globalopt(Agraph_t *root)
+{
+       Agraph_t        *glob;
+
+       glob = globalize(root);
+       fprintf(stderr,"%s: %d crossings\n",agnameof(root),crossings(glob));
+}
+
+/* this assumes one level per node - no mega-nodes */
+static void apply_model(Agraph_t *ug)
+{
+       Agnode_t *un;
+       rep_t                   rep;
+       for (un = agfstnode(ug); un; un = agnxtnode(ug,un))  {
+               rep = association(GD_globalgraph(ug),un);
+               switch (rep.type) {
+               case NODE:
+                       ND_order(un) = ND_order((Agnode_t*)(rep.p));
+                       break;
+               case TALLNODE:
+                       ND_order(un) = ND_order(((vpath_t*)rep.p)->v[0]);
+                       break;
+               default:
+                       abort();
+                       break;
+               }
+       }
+}
+
+/* this is a first cut at a top-level planner.  it's lame. */
+static void rec_cluster_run(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) mincross_clust(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(subg))
+               rec_cluster_run(subg);
+       if (is_a_cluster(ug)) mincross_clust(ug);
+}
+
+/* this is the top level mincross entry point */
+void dot_mincross(Agraph_t *user)
+{
+       rec_cluster_init(user);
+       rec_cluster_run(user);
+       globalopt(user);
+       apply_model(user);
+}
+
+static void invalidate(Agraph_t *g, int rank)
+{
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].crossing_cache.valid = FALSE;
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].candidate = TRUE;
+       if (rank < GD_maxrank(g)) GD_rank(g)[rank+1].candidate = TRUE;
+}
+
+/* swaps two nodes in the same level */
+static void exchange(Agraph_t *g, Agnode_t *u, Agnode_t *v)
+{
+       rank_t  *r;
+       int                     ui,vi,rank;
+
+       assert(ND_rank(u) == ND_rank(v));
+       rank = ND_rank(u);
+       r = &GD_rank(g)[rank];
+       ui = ND_order(u);
+       vi = ND_order(v);
+       ND_order(v) = ui;
+       ND_order(u) = vi;
+       r->v[ND_order(u)] = u;
+       r->v[ND_order(v)] = v;
+       r->crossing_cache.valid = FALSE;
+       r->changed = TRUE;
+       r->candidate = TRUE;    /* old dot had this.  i have qualms. sn */
+       invalidate(g,rank);
+}
+
+int transpose_onerank(Agraph_t* g, int r, boolean reverse)
+{
+       int     i,c0,c1,rv;
+       node_t  *v,*w;
+
+       rv = 0;
+       GD_rank(g)[r].candidate = FALSE;
+       for (i = leftmost(g,r); i < rightmost(g,r); i++) {
+               v = GD_rank(g)[r].v[i];
+               w = GD_rank(g)[r].v[i+1];
+               assert (ND_order(v) < ND_order(w));
+               if (left2right(g,v,w)) continue;
+               c0 = c1 = 0;
+               if (r > GD_minrank(g)) {
+                       c0 += in_cross(v,w);
+                       c1 += in_cross(w,v);
+               }
+               if (r < GD_maxrank(g)) {
+                       c0 += out_cross(v,w);
+                       c1 += out_cross(w,v);
+               }
+               if ((c1 < c0) || ((c0 > 0) && reverse && (c1 == c0))) {
+                       exchange(g,v,w);
+                       rv += (c0 - c1);
+               }
+       }
+       return rv;
+}
+
+static void transpose_sweep(Agraph_t* g, int reverse)
+{
+    int     r,delta;
+
+       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+               GD_rank(g)[r].candidate = TRUE;
+    do {
+                       delta = 0;
+                       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+                               if (GD_rank(g)[r].candidate) delta += transpose_onerank(g,r,reverse);
+    }
+               while (delta >= 1);
+               /* while (delta > crossings(g)*(1.0 - Convergence));*/
+}
+
+static void mincross_sweep(Agraph_t* g, int dir, boolean reverse)
+{
+    int     r,other,low,high,first,last;
+    int     hasfixed;
+
+               low = GD_minrank(g);
+               high = GD_maxrank(g);
+               if (dir == 0) return;
+               if (dir > 0)  { first = low + 1; last = high; dir = 1;}         /* down */
+               else                              { first = high - 1; last = low; dir = -1;}    /* up */
+
+    for (r = first; r != last + dir; r += dir) {
+        other = r - dir;
+        hasfixed = medians(g,r,other);
+        reorder(g,r,reverse,hasfixed);
+    }
+    transpose_sweep(g,NOT(reverse));
+       savebest(g);
+}
+
+
+static int left2right(Agraph_t *g, node_t *v, node_t *w)
+{
+    int         rv;
+
+#ifdef NOTDEF
+    adjmatrix_t *M;
+    M = GD_rank(g)[ND_rank(v)].flat;
+    if (M == NULL) rv = FALSE;
+    else {
+        if (GD_flip(g)) {node_t *t = v; v = w; w = t;}
+        rv = ELT(M,flatindex(v),flatindex(w));
+    }
+#else
+               rv = FALSE;
+#endif
+    return rv;
+}
+
+static void build_flat_graphs(Agraph_t *g)
+{
+}
+
+static int out_cross(node_t *v, node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int  inv, cross = 0,t;
+
+  for (e2 = agfstout(agraphof(w),w); e2; e2 = agnxtout(agraphof(w),e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(aghead(e2));
+               for (e1 = agfstout(agraphof(v),v); e1; e1 = agnxtout(agraphof(v),e1)) {
+                       t = ND_order(aghead(e1)) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_headport(e1).p.x > ED_headport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+                                                                                
+static int in_cross(node_t *v,node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int inv, cross = 0, t;
+                                                                                
+  for (e2 = agfstin(agraphof(w),w); e2; e2 = agnxtin(agraphof(w),e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(agtail(e2));
+               for (e1 = agfstin(agraphof(v),v); e1; e1 = agnxtin(agraphof(v),e1)) {
+                       t = ND_order(agtail(e1)) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_tailport(e1).p.x > ED_tailport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+
+static int int_cmpf(const void *arg0, const void *arg1)
+{
+       return *(int*)arg1 - *(int*)arg0;
+}
+
+/* 8 is the number of bits in port.order, an unsigned char */
+#define VAL(node,port) ((ND_order(node) << 8)  + (port).order)
+
+/*
+ * defines ND_sortweight of each node in r0 w.r.t. r1
+ * returns...
+ */
+static boolean medians(Agraph_t *g, int r0, int r1)
+{
+       static int *list;
+       static int list_extent;
+       int     i,j,lm,rm,lspan,rspan;
+       node_t  *n,**v;
+       edge_t  *e;
+       boolean hasfixed = FALSE;
+
+       if (list_extent < GD_maxinoutdeg(g->root)) {
+               list_extent = GD_maxinoutdeg(g->root);
+               if (!list) list = realloc(list,sizeof(list[0])*list_extent);
+               else list = realloc(list,sizeof(list[0])*list_extent);
+       }
+       v = GD_rank(g)[r0].v;
+       for (i = leftmost(g,r0); i <= rightmost(g,r0); i++) {
+               n = v[i]; j = 0;
+               if (r1 > r0) for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(aghead(e),ED_headport(e));}
+               else for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(agtail(e),ED_tailport(e));}
+               switch(j) {
+                       case 0:
+                               ND_sortweight(n) = MAXINT;              /* no neighbor - median undefined */
+                               ND_sortweight_defined(n) = FALSE;
+                               break;
+                       case 1:
+                               ND_sortweight(n) = list[0];
+                               ND_sortweight_defined(n) = TRUE;
+                               break;
+                       case 2:
+                               ND_sortweight(n) = (list[0] + list[1])/2;
+                               ND_sortweight_defined(n) = TRUE;
+                               break;
+                       default:
+                               qsort(list,j,sizeof(int),int_cmpf);
+                               if (j % 2) ND_sortweight(n) = list[j/2];
+                               else {
+                                       /* weighted median */
+                                       rm = j/2;
+                                       lm = rm - 1;
+                                       rspan = list[j-1] - list[rm];
+                                       lspan = list[lm] - list[0];
+                                       if (lspan == rspan)
+                                               ND_sortweight(n) = (list[lm] + list[rm])/2;
+                                       else {
+                                               int w = list[lm]*rspan + list[rm]*lspan;
+                                               ND_sortweight(n) = w / (lspan + rspan);
+                                       }
+                               }
+                               ND_sortweight_defined(n) = TRUE;
+               }
+       }
+#ifdef NOTDEF
+       /* this code was in the old mincross */
+       for (i = 0; i < GD_rank(g)[r0].n; i++) {
+               n = v[i];
+               if ((ND_out(n).size == 0) && (ND_in(n).size == 0))
+                       hasfixed |= flat_sortweight(n);
+       }
+#endif
+       return hasfixed;
+}
+
+static void reorder(graph_t *g, int r, boolean reverse, boolean hasfixed)
+{
+       boolean changed, muststay;
+       node_t  **vlist, **lp, **rp, **ep;
+       int                     i;
+
+       changed = FALSE;
+       vlist = GD_rank(g)[r].v;
+       ep = &vlist[rightmost(g,r)];
+       
+       for (i = leftmost(g,r); i <= rightmost(g,r); i++) {
+               lp = &vlist[leftmost(g,r)];
+               /* find leftmost node that can be compared */
+               while ((lp < ep) && (!ND_sortweight_defined(*lp))) lp++;
+               if (lp >= ep) break;
+               /* find the node that can be compared */
+               muststay = FALSE;
+               for (rp = lp + 1; rp < ep; rp++) {
+                       if (left2right(g,*lp,*rp)) { muststay = TRUE; break; }
+                       if (ND_sortweight_defined(*rp)) break;  /* weight defined; it's comparable */
+               }
+               if (rp >= ep) break;
+               if (muststay == FALSE) {
+                       register int    p1 = ND_sortweight(*lp);
+                       register int    p2 = ND_sortweight(*rp);
+                       if ((p1 > p2) || ((p1 == p2) && (reverse))) {
+                               exchange(g,*lp,*rp);
+                               changed = TRUE;
+                       }
+               }
+               lp = rp;
+               if ((hasfixed == FALSE) && (reverse == FALSE)) ep--;
+       }
+                                                                                
+       if (changed) {
+               GD_rank(g)[r].changed = TRUE;
+               GD_rank(g)[r].crossing_cache.valid = FALSE;
+               if (r > 0) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+               if (r + 1 > GD_rank(g)[r+1].n) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+       }
+}
+
+static void savebest(graph_t *g)
+{
+       int             nc;
+       Agnode_t        *n;
+
+       nc = crossings(g);
+       if (nc < GD_bestcrossings(g)) {
+               for (n = agfstnode(g); n; n = agnxtnode(g,n))
+                       ND_saveorder(n) = ND_order(n);
+               GD_bestcrossings(g) = nc;
+               GD_lastwin(g) = GD_pass(g);
+       }
+}
+
+static int ND_order_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_order(*(Agnode_t**)arg1) - ND_order(*(Agnode_t**)arg0);
+}
+
+static void restorebest(graph_t *g)
+{
+       Agnode_t        *n;
+       int                     i;
+
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++) 
+               GD_rank(g)[i].changed = FALSE;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))  {
+               if (ND_order(n) != ND_saveorder(n)) {
+                       invalidate(g,ND_rank(n));
+                       GD_rank(g)[i].changed = TRUE;
+                       ND_order(n) = ND_saveorder(n);
+               }
+       }
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++)  {
+               if (GD_rank(g)[i].changed)
+                       qsort(GD_rank(g)[i].v,GD_rank(g)[i].n,sizeof(Agnode_t*),ND_order_cmpf);
+       }
+}
+
+static int crossings(graph_t *g)
+{
+               int             i, rv;
+
+               rv = 0;
+               for (i = GD_minrank(g); i < GD_maxrank(g); i++) {
+                       rv += crossings_below(g,i);
+               }
+               return rv;
+}
+
+/* support function for globalize.  merge two external edge vnode vectors.
+ * generally, we will prefer the vnode from the lowest (closest to a leaf)
+ * cluster, but there can be ties so we work from both ends of the vector
+ * to get symmetry.
+ */
+static void mergevec(Agnode_t **v0, Agnode_t **v1, int low, int high)
+{
+       int     i,j;
+
+       i = low;
+       j = high;
+       while (i <= j) {
+               v0[i] = choosenode(v0[i],v1[i]);
+               v0[j] = choosenode(v1[j],v0[j]);
+               i++;
+               j--;
+       }
+}
+
+/* see above.  fst has to win ties. */
+static Agnode_t *choosenode(Agnode_t *fst, Agnode_t *snd)
+{
+       if (GD_level(agraphof(fst)) <= GD_level(agraphof(snd))) return fst;
+       return snd;
+}
+
+/* get the vnodes of an edge to an EXTNODE but where the
+ * edge 'leaves' the cluster, ascend to its parent.
+ * I am not sure this is really necessary or if it would
+ * suffice to just take the vpath of the edge.
+ */
+static Agnode_t  **peel(Agraph_t *model, vpath_t *path)
+{
+       Agnode_t        **rv, *v;
+       Agraph_t        *pmodel;
+       int             r;
+       repkey_t        repkey;
+       Agedge_t        *origedge;
+       vpath_t         *parpath;
+
+       rv = nodearray(path->low, path->high);
+       for (r = path->low; r <= path->high; r++) {
+               v = path->v[r];
+               if (inrange(GD_minrank(model),r,GD_maxrank(model))) rv[r] = v;
+               else {
+                       pmodel = GD_parent(model);
+                       while (!inrange(GD_minrank(pmodel),r,GD_maxrank(model)) )
+                               model = GD_parent(model);
+                       /* we seem to be assuming 1-1 relationship between origedges and vpath */
+                       repkey = invassociation(model,path);
+                       origedge = repkey.key;
+                       parpath = association(pmodel,origedge).p;
+                       v = parpath->v[r];
+                       rv[r] = v;
+               }
+       }
+       return rv;
+}
+
+/* helper function for globalize */
+static void installglobalobjects(Agraph_t *src, Agraph_t *dst, int r)
+{
+       int                             i;
+       Agnode_t        *v, *vx;
+       Agraph_t        *clustmodel;
+
+       for (i = leftmost(src,r); i <= rightmost(src,r); i++) {
+               v = GD_rank(src)[r].v[i];
+               if ((vx = ND_globalobj(v)))
+                       install(dst,vx);
+               else {
+                       if (ND_type(v) == SKELETON) {
+                               clustmodel = GD_model(ND_cluster(v));
+                               installglobalobjects(clustmodel,dst,r);
+                       }
+                       else {} /* ignore various external edges */
+               }
+       }
+}
+
+/* build the global (flat) graph of the universe.  this is 'flat'
+in the sense that there is one data structure for the entire graph
+(not 'flat' in the sense of flat edges within the same level.)
+*/
+static Agraph_t *globalize(Agraph_t *user)
+{
+       Agraph_t        *glob;
+       Agnode_t        *v;
+       vpath_t         *path, *globpath;
+       int                             i, r;
+       Agnode_t        *n;
+       Agedge_t        *e;
+       rep_t                   rep;
+       rep_t                   globrep, trep, hrep;
+       Agraph_t        *model, *tmodel, *hmodel;
+       Agnode_t        **tvec, **hvec;
+
+       glob = agopen("globalgraph",Agstrictdirected,0);
+       GD_usergraph(glob) = user;
+
+       /* mark the global objects from various clusters */
+       for (n = agfstnode(user); n; n = agnxtnode(user,n)) {
+               globrep = model_intnode(glob, n);
+               model = GD_model(ND_cluster(n));
+               rep = association(model,n);
+               switch(rep.type) {
+               case NODE:
+                       ND_globalobj(v = rep.p) = globrep.p;
+                       break;
+               case TALLNODE:
+                       path = rep.p;
+                       globpath = globrep.p;
+                       for (i = path->low; i < path->high; i++)
+                               ND_globalobj(path->v[i]) = globpath->v[i];
+                       break;
+               default: abort();
+               }
+       }
+       for (n = agfstnode(user); n; n = agnxtnode(user,n)) {
+               for (e = agfstout(user,n); e; e = agnxtout(user,e)) {
+                       globrep = model_edge(glob,e);
+                       tmodel = GD_model(ND_cluster(agtail(e)));
+                       hmodel = GD_model(ND_cluster(aghead(e)));
+                       if (tmodel == hmodel) {         /* easy case */
+                               rep = association(tmodel,e);
+                               assert(rep.type == PATH);
+                               path = rep.p;
+                               globpath = globrep.p;
+                               for (i = path->low; i < path->high; i++)
+                                       ND_globalobj(path->v[i]) = globpath->v[i];
+                       }
+                       else {
+                               trep = association(tmodel,e);
+                               tvec = peel(tmodel,trep.p);
+                               hrep = association(hmodel,e);
+                               hvec = peel(hmodel,hrep.p);
+                               path = (vpath_t*)(trep.p);
+                               mergevec(tvec,hvec,path->low,path->high);
+                               globpath = globrep.p;
+                               for (i = path->low; i <= path->high; i++)
+                                       ND_globalobj(tvec[i]) = globpath->v[i];
+                               freearray(tvec,path->low,sizeof(tvec[0]));
+                               freearray(hvec,path->low,sizeof(hvec[0]));
+                       }
+               }
+       }
+
+       cluster_extents(glob);
+       cluster_rankstorage(glob);
+
+       /* install new representative objects */
+       for (r = GD_minrank(glob); r <= GD_maxrank(glob); r++)
+               installglobalobjects(GD_model(user),glob,r);
+       GD_globalgraph(user) = glob;
+       return glob;
+}
+
+static void countup(Agraph_t *g, rank_t *globr)
+{
+       Agnode_t        *n;
+       Agedge_t        *e;
+       int                             r0, r1, low, high, i;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+                       for (i = 0; i < ND_ranksize(n); i++)
+                               globr[ND_rank(n)+i].n += 1;
+                       for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
+                               r0 = ND_rank(agtail(e));
+                               r1 = ND_rank(aghead(e));
+                               low = MIN(r0,r1);
+                               high = MAX(r0,r1);
+                               for (i = low + 1; i < high; i++)
+                                       globr[i].n += 1;
+                       }
+       }
+}
+
+/* --------- */
+
+static void rec_model_subclusts(Agraph_t *model, Agraph_t *user)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(user))
+               (void) model_clust(model,user);
+       /* note there can be non-cluster subgraphs that contain lower clusters */
+       for (subg = agfstsubg(user); subg; subg = agnxtsubg(subg))
+               rec_model_subclusts(model,subg);
+}
+
+static void cluster_contents(Agraph_t *ug)
+{
+       Agraph_t        *mg, *root, *subg;
+       char *name;
+       Agnode_t        *n;
+       Agedge_t        *e;
+
+       assert(is_a_cluster(ug));
+       assert(GD_model(ug) == 0);
+
+       name = malloc(strlen(agnameof(ug))+10);
+       sprintf(name,"model_%s",agnameof(ug));
+       GD_model(ug) = mg = agopen(name,Agstrictdirected,0);
+       free(name);
+       GD_usergraph(mg) = ug;
+       GD_level(mg) = GD_level(ug);
+
+       /* install internal nodes */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               if (ND_cluster(n) == ug)
+                       model_intnode(mg,n);
+       }
+
+       /* install cluster skeletons */
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(subg))
+               rec_model_subclusts(mg,subg);
+
+       /* install external edge endpoints */
+       root = ug->root;
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(root,n); e; e = agnxtout(root,e)) {
+                       if (!agcontains(ug,agtail(e)))
+                               model_extnode(mg,agtail(e));
+                       if (!agcontains(ug,aghead(e)))
+                               model_extnode(mg,aghead(e));
+               }
+       }
+
+       /* install edges */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(root,n); e; e = agnxtout(root,e)) {
+                       model_edge(mg,e);               /* amazing if this is all it takes */
+               }
+               /* also need to scan in-edges not seen above */
+               for (e = agfstin(root,n); e; e = agnxtin(root,e)) {
+                       if (!agcontains(ug,agtail(e)))
+                               model_edge(mg,e);
+               }
+       }
+}
+
+static void cluster_init(Agraph_t *ug)
+{
+       cluster_contents(ug);
+       cluster_ranksetup(ug);
+       flat_edges(ug);
+       build_ranks(ug,TRUE);
+}
+
+static void rec_cluster_init(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) cluster_init(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(subg))
+               rec_cluster_init(subg);
+}
+
+#ifdef NOTDEF
+/* a given edge can have several other edges (forward or backward)
+   between the same endpoints.  here, we choose one of these to be
+        the canonical representative of those edges. */
+static Agedge_t* canonical_edge(Agedge_t* e)
+{
+       Agraph_t        *g;
+       Agedge_t        *canon;
+
+       g = agraphof(e);
+       if (ND_rank(aghead(e)) > ND_rank(agtail(e)))
+               canon = agfindedge(g,agtail(e),aghead(e));
+       else {
+               if 
+       }
+}
+#endif
+
+static void cluster_extents(Agraph_t *g)
+{
+       Agnode_t        *n;
+       Agedge_t        *e;
+       short                   indeg, outdeg;
+
+       /* find minrank, maxrank, in/out/max node degrees */
+       n = agfstnode(g);
+       GD_minrank(g) = GD_maxrank(g) = ND_rank(n);
+       GD_maxinoutdeg(g) = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               if (GD_maxrank(g) < ND_rank(n)) GD_maxrank(g) = ND_rank(n);
+               if (GD_minrank(g) > ND_rank(n)) GD_minrank(g) = ND_rank(n);
+               if (ND_indeg(n) == 0) {
+                       indeg = 0; for (e = agfstin(g,n); e; e = agnxtin(g,e)) indeg++;
+                       ND_indeg(n) = indeg;
+               }
+               if (ND_outdeg(n) == 0) {
+                       outdeg = 0; for (e = agfstout(g,n); e; e = agnxtout(g,e)) outdeg++;
+                       ND_outdeg(n) = outdeg;
+               }
+               GD_maxinoutdeg(g) = MAX(GD_maxinoutdeg(g),MAX(indeg,outdeg));
+       }
+}
+
+/* utility function to allocate GD_rank and GD_rank(g)[r].v */
+static void cluster_rankstorage(Agraph_t *g)           /* g is a model graph */
+{
+       Agnode_t        *n;
+       int                             minr,maxr,r,*ranksize;
+
+       /* initialize storage for ranks in the model; nodes are installed later */
+       minr = GD_minrank(g);
+       maxr = GD_maxrank(g);
+       ranksize = intarray(minr,maxr);
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ranksize[ND_rank(n)]++;
+       GD_rank(g) = T_array(minr,maxr,sizeof(rank_t));
+       for (r = minr; r <= maxr; r++)
+               GD_rank(g)[r].v = N_NEW(ranksize[r]+1,Agnode_t*);       /* NIL at end */
+       freearray(ranksize,minr,sizeof(ranksize[0]));
+}
+
+/* set GD_minrank, GD_maxrank, ND_indeg, ND_outdeg, GD_maxinoutdeg
+ * allocate GD_rank and GD_rank(g)[r].v
+ */
+static void cluster_ranksetup(Agraph_t *ug)
+{
+       Agraph_t        *g;
+
+       g = GD_model(ug);
+       if (agfstnode(g) == NILnode) return;
+
+       cluster_extents(g);
+       cluster_rankstorage(g);
+
+       /* set up mincross running parameters */
+       GD_pass(g) = 0;
+       GD_lastwin(g) = 0;
+       GD_mintry(g) = gvgetint(ug,"minpass",24);
+       GD_maxpass(g) = gvgetint(ug,"maxpass",1024);
+       GD_bestcrossings(g) = MAXINT;
+}
+
+static int printfn(Dict_t *dict, void *arg, void *data)
+{
+       repkey_t *rk = arg;
+       printf("%lx=>%d,%lx\n",(long unsigned int)(rk->key),rk->val.type,(long unsigned int)(rk->val.p));
+       return 0;
+}
+static void dumpdict(Agraph_t *model)
+{
+       dtwalk(repdict(model),printfn, 0);
+}
diff --git a/lib/dotgen2/newdot.h b/lib/dotgen2/newdot.h
new file mode 100644 (file)
index 0000000..16072ae
--- /dev/null
@@ -0,0 +1,287 @@
+/*********** external interfaces *********/
+#ifdef NOTDEF
+#include <vmalloc.h>
+#endif
+#include <assert.h>
+#include <string.h>
+//#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+/* #include <values.h> */
+#include <cdt.h>
+#include <cgraph.h>
+#include "radix.h"
+
+/*********** a few useful types *********/
+typedef unsigned char boolean;
+typedef struct {double x, y;} point;
+typedef struct {point LL, UR;} box;
+
+typedef struct Agraph_s        graph_t;
+typedef struct Agnode_s        node_t;
+typedef struct Agedge_s        edge_t;
+
+/*********** types needed by graphs *********/
+typedef struct adjmatrix_t {   /* for flat edge constraints */
+       int             nrows,ncols;
+       char    *data;
+} adjmatrix_t;
+#define ELT(M,i,j)         (M->data[((i)*M->ncols)+(j)])
+
+typedef struct rank_s {                 /* describes the contents of a rank */
+       node_t  **v;            /* the vertex list */
+       int             n;                              /* its extent */
+
+       /* for counting edge crossings */
+       radixrec_t      *edgelist;      /* list of out (south) edges */
+       int             ne;                     /* extent of this list */
+       int             *tree, treesize;        /* the crossing accumulator tree */
+
+       adjmatrix_t     *flat;                  /* transitive closure of the left-right constraints */
+
+       struct crossing_cache_s {
+               int count;                                              /* crossings between this level and this + 1 */
+               boolean valid;
+       } crossing_cache;       
+
+       boolean candidate;                              /* true if an adjacent level changed */
+       boolean changed;
+} rank_t;
+
+typedef struct rep_s {
+       void            *p;             /* must always be an Agraph_t*, Agnode_t*, Agedge_t*  */
+       int                     type;   /* NODE, TALLNODE, PATH, SKELETON, EXTNODE */
+} rep_t;
+
+typedef struct repkey_s {
+       void            *key;
+       rep_t           val;
+} repkey_t;
+
+typedef struct vpath_s {
+       int                             low, high;
+       node_t          **v;                                                    /* virtual nodes of path */
+       edge_t          **e;                                                    /* virtual edges of path indexed by tail rank */
+       float                   avgtailport, avgheadport; /* total port offsets */
+       float                   weight;
+} vpath_t;
+
+
+/*********** graphs *********/
+typedef struct Agraphinfo_s {
+       Agrec_t hdr;
+       struct Agraph_s *parent;                /* containing cluster (not parent subgraph) */
+       union {
+               struct Agraph_s *user;  /* points from a model graph back to client obj */
+               struct Agraph_s *global; /* points from user root graph to global model */
+       } graph;
+       int             level;                                                          /* cluster nesting level (not node level!) */
+       node_t  *minrep, *maxrep;       /* set leaders for min and max rank */
+       boolean has_sourcerank, has_sinkrank;
+       int                     minrank,maxrank;
+
+       /* these control mincross */
+       struct Agraph_s *graphrep;      /* graph that models this cluster for mincross */
+       rank_t  *rank;
+       int                     pass;                   /* a simple counter */
+       int                     lastwin;        /* last pass that was an improvement */
+       int                     mintry;         /* limit to losing passes */
+       int                     maxpass;        /* limit to total passes */
+       int                     bestcrossings;  /* current best solution */
+       Dict_t  *repdict;
+
+       /* other mincross values */
+       int                     maxinoutdeg;            /* maximum of all in and out degrees */
+} Agraphinfo_t;
+
+
+#define GD_FIELD(g,field)  (((Agraphinfo_t*)((g)->base.data))->field)
+#define GD_parent(g)   GD_FIELD(g,parent)
+#define GD_level(g)            GD_FIELD(g,level)
+#define GD_minrep(g)   GD_FIELD(g,minrep)
+#define GD_maxrep(g)   GD_FIELD(g,maxrep)
+#define GD_has_sourcerank(g)   GD_FIELD(g,has_sourcerank)
+#define GD_has_sinkrank(g)             GD_FIELD(g,has_sinkrank)
+#define GD_model(g)            GD_FIELD(g,graphrep)
+#define GD_rank(g)             GD_FIELD(g,rank)
+#define GD_minrank(g)          GD_FIELD(g,minrank)
+#define GD_maxrank(g)          GD_FIELD(g,maxrank)
+#define GD_pass(g)                     GD_FIELD(g,pass)
+#define GD_lastwin(g)          GD_FIELD(g,lastwin)
+#define GD_mintry(g)           GD_FIELD(g,mintry)
+#define GD_maxpass(g)          GD_FIELD(g,maxpass)
+#define GD_bestcrossings(g)            GD_FIELD(g,bestcrossings)
+#define GD_maxinoutdeg(g)              GD_FIELD(g,maxinoutdeg)
+#define GD_repdict(g)          GD_FIELD(g,repdict)
+#define GD_usergraph(g)                GD_FIELD(g,graph.user)
+#define GD_globalgraph(g)       GD_FIELD(g,graph.global)
+
+typedef struct nlist_s {
+       int                     size;
+       node_t          **list;
+} nlist_t;
+
+typedef struct elist_s {
+       int                     size;
+       edge_t          **list;
+} elist_t;
+
+typedef struct queue {
+    node_t  **store,**limit,**head,**tail;
+} queue;
+
+/*********** nodes *********/
+typedef struct Agnodeinfo_s {
+       Agrec_t hdr;
+       int             rank;                           /* network simplex solver */
+       graph_t *cluster;                       /* lowest containing cluster */
+       node_t  *set;                           /* for union-find */
+
+       union {
+               node_t  *v;
+               edge_t  *e;
+               graph_t *g;
+       } rep;
+       int     type;
+
+/* tags for representative (model) objects */
+/* 0 is reserved for undefined */
+#define NODE                                           1                       /* a real node on only one rank */
+#define TALLNODE                               2                       /* a real node on two or more ranks */
+#define EXTNODE                                        3                       /* a node external to a cluster */
+#define SKELETON                               4
+#define PATH                                           5
+
+               /* for network-simplex */
+       elist_t tree_in;
+       elist_t tree_out;
+       int             priority;
+       int             low,lim;
+       edge_t  *par;
+
+               /* for mincross */
+       short           indeg, outdeg;
+       int                     order;
+       int                     saveorder;              /* for saving the best solution */
+       int                     component;
+       int                     ranksize;                       /* height as counted in number of levels */
+       int                     sortweight;             /* median, barycenter, etc. */
+       boolean mark, onstack, sortweight_defined;
+} Agnodeinfo_t;
+
+#define ND_FIELD(n,field)  (((Agnodeinfo_t*)((n)->base.data))->field)
+#define ND_globalobj(n)        ND_FIELD(n,rep.v)
+#define ND_rep(n)              ND_FIELD(n,rep.v)
+#define ND_cluster(n)  ND_FIELD(n,cluster)
+#define ND_cluster(n)  ND_FIELD(n,cluster)
+#define ND_type(n)             ND_FIELD(n,type)
+#define ND_set(n)              ND_FIELD(n,set)
+#define ND_mark(n)             ND_FIELD(n,mark)
+#define ND_onstack(n)  ND_FIELD(n,onstack)
+#define ND_rank(n)             ND_FIELD(n,rank)
+#define ND_tree_in(n)  ND_FIELD(n,tree_in)
+#define ND_tree_out(n) ND_FIELD(n,tree_out)
+#define ND_priority(n) ND_FIELD(n,priority)
+#define ND_low(n)              ND_FIELD(n,low)
+#define ND_lim(n)              ND_FIELD(n,lim)
+#define ND_par(n)              ND_FIELD(n,par)
+#define        ND_indeg(n)             ND_FIELD(n,indeg)
+#define        ND_outdeg(n)    ND_FIELD(n,outdeg)
+#define ND_order(n)            ND_FIELD(n,order)
+#define ND_saveorder(n)                ND_FIELD(n,saveorder)
+#define ND_component(n)                ND_FIELD(n,component)
+#define ND_sortweight(n)       ND_FIELD(n,sortweight)
+#define ND_sortweight_defined(n)       ND_FIELD(n,sortweight_defined)
+#define ND_ranksize(n)         ND_FIELD(n,ranksize)
+
+/********** edges **********/
+typedef struct port_s { /* internal edge endpoint specification */
+  point   p;          /* aiming point */
+  double    theta;    /* slope in radians */
+  box*    bp;         /* if not null, points to bbox of
+                                                                                        * rectangular area that is port target
+                                                                                        */
+  boolean constrained,defined;
+  unsigned char order;      /* for mincross (?) */
+} port_t;
+
+typedef struct Agedgeinfo_s {
+       Agrec_t hdr;
+       /* to assign levels */
+       float           minlen;
+       float           weight;
+       int                     tree_index;
+       int                     cutval;
+
+       /* to run mincross */
+       int                     xpenalty;               /* crossing weight */
+       port_t  tailport, headport;
+
+       vpath_t         *path;
+
+} Agedgeinfo_t;
+
+#define ED_FIELD(e,field)  (((Agedgeinfo_t*)((e)->base.data))->field)
+#define ED_minlen(e)   ED_FIELD(e,minlen)
+#define ED_weight(e)   ED_FIELD(e,weight)
+#define ED_tree_index(e) ED_FIELD(e,tree_index)
+#define ED_cutval(e)   ED_FIELD(e,cutval)
+#define ED_xpenalty(e) ED_FIELD(e,xpenalty)
+#define ED_tailport(e) ED_FIELD(e,tailport)
+#define ED_headport(e) ED_FIELD(e,headport)
+#define ED_orig(e)              ED_FIELD(e,orig)
+
+
+/********** variable constants **********/
+#define                BACKWARD_PENALTY        1000
+#define                STRONG_CLUSTER_WEIGHT   1000
+#define                VEDGE_PENALTY   1
+#define   CEDGE_PENALTY                (1<<30)-1
+#define                PORTRANGE                               1e+6    /* port coordinates cannot be bigger than this */
+
+       /* xpos phase */
+#define        SHORT_FACTOR            1
+#define                LONG_FACTOR                     2
+#define                LONG_END_FACTOR 4
+
+/********** useful symbols **********/
+#define                MINRANK         1
+#define        SOURCERANK      2
+#define                MAXRANK         3
+#define                SINKRANK        4
+#define                SAMERANK        5
+#define                NORANK          6
+
+
+
+/********** libgraph extensions **********/
+int                    mapbool(char *str, int defval);
+
+#ifndef NIL
+#define NIL(t) ((t)0)  /* agrees with CDT */
+#endif
+#define NILgraph       NIL(Agraph_t*)
+#define NILnode                NIL(Agnode_t*)
+#define NILedge                NIL(Agedge_t*)
+
+
+/********** useful macros **********/
+#ifndef MAXINT
+#define MAXINT (int)(~((int)0) ^ (1 << (sizeof(int)*8-1)))
+#endif
+#define MAX(a,b)       ((a)>=(b)?(a):(b))
+#define MIN(a,b)       ((a)<=(b)?(a):(b))
+#define NEW(t)      ((t*)zmalloc(sizeof(t)))
+#define N_NEW(n,t)  ((t*)zmalloc((n)*sizeof(t)))
+#define alloc_elist(n,L)      do {L.size = 0; L.list = N_NEW(n + 1,edge_t*); } while (0)
+#define free_list(L)          do {if (L.list) free(L.list);} while (0)
+#define ALLOC(size,ptr,type) (ptr? (type*)realloc(ptr,(size)*sizeof(type)):(type*)malloc((size)*sizeof(type)))
+
+
+/********** graphviz entry points **********/
+extern int             dot_Verbose;
+void                                   rank(graph_t *g, int balance, int maxiter);
+
+Agraph_t                       *dot_lca(Agraph_t *c0, Agraph_t *c1);
+
+#include "au_.h"
diff --git a/lib/dotgen2/ns.c b/lib/dotgen2/ns.c
new file mode 100644 (file)
index 0000000..1abaeeb
--- /dev/null
@@ -0,0 +1,704 @@
+/* 
+ * Network Simplex Algorithm for Ranking Nodes of a DAG
+ */
+
+#include "newdot.h"
+
+static int init_graph(graph_t *);
+static void dfs_cutval(node_t* v, edge_t* par);
+static int dfs_range(node_t* v, edge_t* par, int low);
+static int x_val(edge_t* e, node_t* v, int dir);
+
+#define LENGTH(e)              (ND_rank(aghead(e)) - ND_rank(agtail(e)))
+#define SLACK(e)               (LENGTH(e) - ED_minlen(e))
+#define SEQ(a,b,c)             (((a) <= (b)) && ((b) <= (c)))
+#define TREE_EDGE(e)   (ED_tree_index(e) >= 0)
+
+static graph_t *G;
+static int             N_nodes,N_edges;
+static int             Minrank,Maxrank;
+static int             S_i;            /* search index for enter_edge */
+static int             Search_size;
+#define SEARCHSIZE 30
+static elist_t Tree_edge;
+static nlist_t Tree_node;
+
+static void 
+add_tree_edge(edge_t* e)
+{
+       node_t  *n;
+       if (TREE_EDGE(e)) abort();
+       ED_tree_index(e) = Tree_edge.size;
+       Tree_edge.list[Tree_edge.size++] = e;
+       if (ND_mark(agtail(e)) == FALSE) Tree_node.list[Tree_node.size++] = agtail(e);
+       if (ND_mark(aghead(e)) == FALSE) Tree_node.list[Tree_node.size++] = aghead(e);
+       n = agtail(e);
+       ND_mark(n) = TRUE;
+       ND_tree_out(n).list[ND_tree_out(n).size++] = e;
+       ND_tree_out(n).list[ND_tree_out(n).size] = NULL;
+       n = aghead(e);
+       ND_mark(n) = TRUE;
+       ND_tree_in(n).list[ND_tree_in(n).size++] = e;
+       ND_tree_in(n).list[ND_tree_in(n).size] = NULL;
+}
+
+static void 
+exchange_tree_edges(edge_t *e,edge_t *f)
+{
+       int             i,j;
+       node_t  *n;
+
+       ED_tree_index(f) = ED_tree_index(e);
+       Tree_edge.list[ED_tree_index(e)] = f;
+       ED_tree_index(e) = -1;
+
+       n = agtail(e);
+       i = --(ND_tree_out(n).size);
+       for (j = 0; j <= i; j++) if (ND_tree_out(n).list[j] == e) break;
+       ND_tree_out(n).list[j] = ND_tree_out(n).list[i];
+       ND_tree_out(n).list[i] = NULL;
+       n = aghead(e);
+       i = --(ND_tree_in(n).size);
+       for (j = 0; j <= i; j++) if (ND_tree_in(n).list[j] == e) break;
+       ND_tree_in(n).list[j] = ND_tree_in(n).list[i];
+       ND_tree_in(n).list[i] = NULL;
+
+       n = agtail(f);
+       ND_tree_out(n).list[ND_tree_out(n).size++] = f;
+       ND_tree_out(n).list[ND_tree_out(n).size] = NULL;
+       n = aghead(f);
+       ND_tree_in(n).list[ND_tree_in(n).size++] = f;
+       ND_tree_in(n).list[ND_tree_in(n).size] = NULL;
+}
+
+static
+void init_rank(void)
+{
+       int                     ctr;
+       queue           *Q;
+       node_t          *v;
+       edge_t          *e;
+
+       Q = new_queue(N_nodes);
+       ctr = 0;
+
+       for (v = agfstnode(G); v; v = agnxtnode(G,v)) {
+               if (ND_priority(v) == 0) enqueue(Q,v);
+       }
+
+       while ((v = dequeue(Q))) {
+               ND_rank(v) = 0;
+               ctr++;
+               for (e = agfstin(G,v); e; e = agnxtin(G,e))
+                       ND_rank(v) = MAX(ND_rank(v),ND_rank(agtail(e)) + ED_minlen(e));
+               for (e = agfstout(G,v); e; e = agnxtout(G,e))
+                       if (--(ND_priority(aghead(e))) <= 0) enqueue(Q,aghead(e));
+       }
+       if (ctr != N_nodes) {
+               agerr(AGERR, "trouble in init_rank\n");
+               for (v = agfstnode(G); v; v = agnxtnode(G,v)) {
+                       if (ND_priority(v))
+                               agerr(AGPREV, "\t%s %d\n",agnameof(v),ND_priority(v));
+               }
+       }
+       free_queue(Q);
+}
+
+static node_t *
+incident(edge_t* e)
+{
+       if (ND_mark(agtail(e))) {
+               if (ND_mark(aghead(e)) == FALSE)
+                       return agtail(e);
+       }
+       else {
+               if (ND_mark(aghead(e)))
+                       return aghead(e);
+       }
+       return NULL;
+}
+
+static edge_t *
+leave_edge (void)
+{
+       edge_t                  *f,*rv = NULL;
+       int                             j,cnt = 0;
+
+       j = S_i;
+       while (S_i < Tree_edge.size) {
+               if (ED_cutval((f = Tree_edge.list[S_i])) < 0) {
+                       if (rv) {
+                               if (ED_cutval(rv) > ED_cutval(f)) rv = f;
+                       }
+                       else rv = Tree_edge.list[S_i];
+                       if (++cnt >= Search_size) return rv;
+               }
+               S_i++;
+       }
+       if (j > 0) {
+               S_i = 0;
+               while (S_i < j) {
+                       if (ED_cutval((f = Tree_edge.list[S_i])) < 0) {
+                               if (rv) {
+                                       if (ED_cutval(rv) > ED_cutval(f)) rv = f;
+                               }
+                               else rv = Tree_edge.list[S_i];
+                               if (++cnt >= Search_size) return rv;
+                       }
+                       S_i++;
+               }
+       }
+       return rv;
+}
+
+static edge_t  *Enter;
+static int             Low,Lim,Slack;
+
+static void 
+dfs_enter_outedge(node_t* v)
+{
+       int             i, slack;
+       edge_t  *e;
+
+       for (e = agfstout(G,v); e; e = agnxtout(G,e)) {
+               if (TREE_EDGE(e) == FALSE) {
+                       if (!SEQ(Low,ND_lim(aghead(e)),Lim)) {
+                               slack = SLACK(e);
+                               if ((slack < Slack) || (Enter == NULL)) {
+                                       Enter = e;
+                                       Slack = slack;
+                               }
+                       }
+               }
+               else if (ND_lim(aghead(e)) < ND_lim(v)) dfs_enter_outedge(aghead(e));
+       }
+       for (i = 0; (e = ND_tree_in(v).list[i]) && (Slack > 0); i++)
+               if (ND_lim(agtail(e)) < ND_lim(v)) dfs_enter_outedge(agtail(e));
+}
+
+static void 
+dfs_enter_inedge(node_t* v)
+{
+       int             i,slack;
+       edge_t  *e;
+
+       for (e = agfstin(G,v); e; e = agnxtin(G,e)) {
+               if (TREE_EDGE(e) == FALSE) {
+                       if (!SEQ(Low,ND_lim(agtail(e)),Lim)) {
+                               slack = SLACK(e);
+                               if ((slack < Slack) || (Enter == NULL)) {
+                                       Enter = e;
+                                       Slack = slack;
+                               }
+                       }
+               }
+               else if (ND_lim(agtail(e)) < ND_lim(v)) dfs_enter_inedge(agtail(e));
+       }
+       for (i = 0; (e = ND_tree_out(v).list[i]) && (Slack > 0); i++)
+               if (ND_lim(aghead(e)) < ND_lim(v)) dfs_enter_inedge(aghead(e));
+}
+
+static edge_t *
+enter_edge(edge_t* e)
+{
+       node_t  *v;
+       int             outsearch;
+
+       /* v is the down node */
+       if (ND_lim(agtail(e)) < ND_lim(aghead(e))) {v = agtail(e); outsearch = FALSE;}
+       else {v = aghead(e);outsearch = TRUE;}
+       Enter = NULL;
+       Slack = MAXINT;
+       Low = ND_low(v);
+       Lim = ND_lim(v);
+       if (outsearch) dfs_enter_outedge(v);
+       else dfs_enter_inedge(v);
+       return Enter;
+}
+
+static int 
+treesearch(node_t* v)
+{
+       edge_t  *e;
+
+       for (e = agfstout(G,v); e; e = agnxtout(G,e)) {
+               if ((ND_mark(aghead(e)) == FALSE) && (SLACK(e) == 0)) {
+                       add_tree_edge(e);
+                       if ((Tree_edge.size == N_nodes-1) || treesearch(aghead(e))) return TRUE;
+               }
+       }
+       for (e = agfstin(G,v); e; e = agnxtin(G,e)) {
+               if ((ND_mark(agtail(e)) == FALSE) && (SLACK(e) == 0)) {
+                       add_tree_edge(e);
+                       if ((Tree_edge.size == N_nodes-1) || treesearch(agtail(e))) return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+static int
+tight_tree(void)
+{
+       int             i;
+       node_t  *n;
+
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               ND_mark(n) = FALSE;
+               ND_tree_in(n).list[0] = ND_tree_out(n).list[0] = NULL;
+               ND_tree_in(n).size = ND_tree_out(n).size = 0;
+       }
+       for (i = 0; i < Tree_edge.size; i++) ED_tree_index(Tree_edge.list[i]) = -1;
+
+       Tree_node.size = Tree_edge.size = 0;
+       for (n = agfstnode(G); n && (Tree_edge.size == 0); n = agnxtnode(G,n))
+               treesearch(n);
+       return Tree_node.size;
+}
+
+static void 
+init_cutvalues(void)
+{
+       dfs_range(agfstnode(G),NULL,1);
+       dfs_cutval(agfstnode(G),NULL);
+}
+
+static void 
+feasible_tree(void)
+{
+       int                     i,delta;
+       node_t          *n;
+       edge_t          *e,*f;
+
+       if (N_nodes <= 1) return;
+       while (tight_tree() < N_nodes) {
+               e = NULL;
+               for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+                       for (f = agfstout(G,n); f; f = agnxtout(G,f)) {
+                               if ((TREE_EDGE(f) == FALSE) && incident(f) && ((e == NULL)
+                                       || (SLACK(f) < SLACK(e)))) e = f;
+                       }
+               }
+               if (e) {
+                       delta = SLACK(e);
+                       if (delta) {
+                               if (incident(e) == aghead(e)) delta = -delta;
+                               for (i = 0; i < Tree_node.size; i++)
+                                       ND_rank(Tree_node.list[i]) += delta;
+                       }
+               }
+               else {
+#ifdef DEBUG
+                       fprintf(stderr,"not in tight tree:\n");
+                       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+                               for (i = 0; i < Tree_node.size; i++)
+                                       if (Tree_node.list[i] == n) break;
+                               if (i >= Tree_node.size) fprintf(stderr,"\t%s\n",agnameof(n));
+                       }
+#endif
+                       abort();
+               }
+       }
+       init_cutvalues();
+}
+
+/* walk up from v to LCA(v,w), setting new cutvalues. */
+static node_t *
+treeupdate(node_t *v, node_t *w, int cutvalue, int dir)
+{
+       edge_t  *e;
+       int             d;
+
+       while (!SEQ(ND_low(v),ND_lim(w),ND_lim(v))) {
+               e = ND_par(v);
+               if (v == agtail(e)) d = dir; else d = NOT(dir);
+               if (d) ED_cutval(e) += cutvalue; else ED_cutval(e) -= cutvalue;
+               if (ND_lim(agtail(e)) > ND_lim(aghead(e))) v = agtail(e); else v = aghead(e);
+       }
+       return v;
+}
+
+static void 
+rerank(node_t* v, int delta)
+{
+       int             i;
+       edge_t  *e;
+
+       ND_rank(v) -= delta;
+       for (i = 0; (e = ND_tree_out(v).list[i]); i++) 
+               if (e != ND_par(v)) rerank(aghead(e),delta);
+       for (i = 0; (e = ND_tree_in(v).list[i]); i++) 
+               if (e != ND_par(v)) rerank(agtail(e),delta);
+}
+
+/* e is the tree edge that is leaving and f is the nontree edge that
+ * is entering.  compute new cut values, ranks, and exchange e and f.
+ */
+void 
+update(edge_t *e, edge_t *f)
+{
+       int             cutvalue,delta;
+       node_t  *lca;
+
+       delta = SLACK(f);
+       /* "for (v = in nodes in tail side of e) do ND_rank(v) -= delta;" */
+       if (delta > 0) {
+               int s;
+               s = ND_tree_in(agtail(e)).size + ND_tree_out(agtail(e)).size;
+               if (s == 1) rerank(agtail(e),delta);
+               else {
+                       s = ND_tree_in(aghead(e)).size + ND_tree_out(aghead(e)).size;
+                       if (s == 1) rerank(aghead(e),-delta);
+                       else {
+                               if (ND_lim(agtail(e)) < ND_lim(aghead(e))) rerank(agtail(e),delta);
+                               else rerank(aghead(e),-delta);
+                       }
+               }
+       }
+
+       cutvalue = ED_cutval(e);
+       lca = treeupdate(agtail(f),aghead(f),cutvalue,1);
+       if (treeupdate(aghead(f),agtail(f),cutvalue,0) != lca) abort();
+       ED_cutval(f) = -cutvalue;
+       ED_cutval(e) = 0;
+       exchange_tree_edges(e,f);
+       dfs_range(lca,ND_par(lca),ND_low(lca));
+}
+
+static void 
+scan_and_normalize(void)
+{
+       node_t  *n;
+
+       Minrank = MAXINT;
+       Maxrank = -MAXINT;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               Minrank = MIN(Minrank,ND_rank(n));
+               Maxrank = MAX(Maxrank,ND_rank(n));
+       }
+       if (Minrank != 0) {
+               for (n = agfstnode(G); n; n = agnxtnode(G,n))
+                       ND_rank(n) -= Minrank;
+               Maxrank -= Minrank;
+               Minrank = 0;
+       }
+}
+
+static void 
+LR_balance(void)
+{
+       int             i,delta;
+       node_t  *n;
+       edge_t  *e,*f;
+
+       for (i = 0; i < Tree_edge.size; i++) {
+               e = Tree_edge.list[i];
+               if (ED_cutval(e) == 0) {
+                       f = enter_edge(e);
+                       if (f == NULL) continue;
+                       delta = SLACK(f);
+                       if (delta <= 1) continue;
+                       if (ND_lim(agtail(e)) < ND_lim(aghead(e))) rerank(agtail(e),delta/2);
+                       else rerank(aghead(e),-delta/2);
+               }
+       }
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               free_list(ND_tree_in(n));
+               free_list(ND_tree_out(n));
+               ND_mark(n) = FALSE;
+       }
+}
+
+static int
+countable_node(node_t *n)
+{
+       return TRUE;            /* could be false for slacknodes */
+}
+
+static void 
+TB_balance(void)
+{
+       node_t  *n;
+       edge_t  *e;
+       int             i,low,high,choice,*nrank;
+       int             inweight,outweight;
+
+       scan_and_normalize();
+
+       /* find nodes that are not tight and move to less populated ranks */
+       nrank = N_NEW(Maxrank+1,int);
+       for (i = 0; i <= Maxrank; i++) nrank[i] = 0;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n))
+               if (countable_node(n)) nrank[ND_rank(n)]++;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               if (!countable_node(n)) continue;
+               inweight = outweight = 0;
+               low = 0;
+               high = Maxrank;
+               for (e = agfstin(G,n); e; e = agnxtin(G,e)) {
+                       inweight += ED_weight(e);
+                       low = MAX(low,ND_rank(agtail(e)) + ED_minlen(e));
+               }
+               for (e = agfstout(G,n); e; e = agnxtout(G,e)) {
+                       outweight += ED_weight(e);
+                       high = MIN(high,ND_rank(aghead(e)) - ED_minlen(e));
+               }
+               if (low < 0) low = 0;   /* vnodes can have ranks < 0 */
+               if (inweight == outweight) {
+                       choice = low;
+                       for (i = low + 1; i <= high; i++)
+                               if (nrank[i] < nrank[choice]) choice = i;
+                       nrank[ND_rank(n)]--; nrank[choice]++;
+                       ND_rank(n) = choice;
+               }
+               free_list(ND_tree_in(n));
+               free_list(ND_tree_out(n));
+               ND_mark(n) = FALSE;
+       }
+       free(nrank);
+}
+
+static int
+init_graph(graph_t* g)
+{
+       int             i,feasible;
+       node_t  *n;
+       edge_t  *e;
+
+       G = g;
+       N_nodes = N_edges = S_i = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               ND_mark(n) = FALSE;
+               N_nodes++;
+               for (e = agfstout(g,n); e; e = agnxtout(g,e)) N_edges++;
+       }
+
+       Tree_node.list = ALLOC(N_nodes,Tree_node.list,node_t*);
+       Tree_node.size = 0;
+       Tree_edge.list = ALLOC(N_nodes,Tree_edge.list,edge_t*);
+       Tree_edge.size = 0;
+
+       feasible = TRUE;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               ND_priority(n) = 0;
+               i = 0;
+               for (e = agfstin(G,n); e; e = agnxtin(G,e)) {
+                       i++;
+                       ND_priority(n)++;
+                       ED_cutval(e) = 0;
+                       ED_tree_index(e) = -1;
+                       if (feasible && (ND_rank(aghead(e)) - ND_rank(agtail(e)) < ED_minlen(e)))
+                               feasible = FALSE;
+               }
+               ND_tree_in(n).list = N_NEW(i+1,edge_t*);
+               ND_tree_in(n).size = 0;
+               for (e = agfstout(G,n); e; e = agnxtout(G,e)) i++;
+               ND_tree_out(n).list = N_NEW(i+1,edge_t*);
+               ND_tree_out(n).size = 0;
+       }
+       return feasible;
+}
+
+void 
+rank(graph_t *g, int balance, int maxiter)
+{
+       int     iter = 0,feasible;
+       char    *s,*ns = "network simplex: ";
+       edge_t  *e,*f;
+
+       if (dot_Verbose) start_timer();
+       feasible = init_graph(g);
+       if (!feasible) init_rank();
+       if (maxiter <= 0) return;
+
+       if ((s = agget(g,"searchsize"))) Search_size = atoi(s);
+       else Search_size = SEARCHSIZE;
+
+       feasible_tree();
+       while ((e = leave_edge())) {
+               f = enter_edge(e);
+               update(e,f);
+               iter++;
+               if (dot_Verbose && (iter % 100 == 0)) {
+                       if (iter % 1000 == 100) fputs(ns,stderr);
+                       fprintf(stderr,"%d ",iter);
+                       if (iter % 1000 == 0) fputc('\n',stderr);
+               }
+               if (iter >= maxiter) break;
+       }
+       switch(balance){
+       case 1: TB_balance(); break;
+       case 2: LR_balance(); break;
+       default: scan_and_normalize(); break;
+       }
+       if (dot_Verbose) {
+               if (iter >= 100) fputc('\n',stderr);
+               fprintf(stderr,"%s%d nodes %d edges %d iter %.2f sec\n",
+                       ns,N_nodes,N_edges,iter,elapsed_sec());
+       }
+}
+
+/* set cut value of f, assuming values of edges on one side were already set */
+static void 
+x_cutval(edge_t* f)
+{
+       node_t  *v;
+       edge_t  *e;
+       int             sum,dir;
+
+       /* set v to the node on the side of the edge already searched */
+       if (ND_par(agtail(f)) == f) { v = agtail(f); dir = 1; }
+       else { v = aghead(f); dir = -1; }
+
+       sum = 0;
+       for (e = agfstout(G,v); e; e = agnxtout(G,e)) sum += x_val(e,v,dir);
+       for (e = agfstin(G,v); e; e = agnxtin(G,e)) sum += x_val(e,v,dir);
+       ED_cutval(f) = sum;
+}
+
+static int 
+x_val(edge_t* e, node_t* v, int dir)
+{
+       node_t  *other;
+       int             d,rv,f;
+
+       if (agtail(e) == v) other = aghead(e); else other = agtail(e);
+       if (!(SEQ(ND_low(v),ND_lim(other),ND_lim(v)))) {f = 1; rv = ED_weight(e);}
+       else {
+               f = 0;
+               if (TREE_EDGE(e)) rv = ED_cutval(e);
+               else rv = 0;
+               rv -= ED_weight(e);
+       }
+       if (dir > 0) {if (aghead(e) == v) d = 1; else d = -1;}
+       else {if (agtail(e) == v) d = 1; else d = -1; }
+       if (f) d = -d;
+       if (d < 0) rv = -rv;
+       return rv;
+}
+
+static void 
+dfs_cutval(node_t* v, edge_t* par)
+{
+       int             i;
+       edge_t  *e;
+
+       for (i = 0; (e = ND_tree_out(v).list[i]); i++)
+               if (e != par) dfs_cutval(aghead(e),e);
+       for (i = 0; (e = ND_tree_in(v).list[i]); i++)
+               if (e != par) dfs_cutval(agtail(e),e);
+       if (par) x_cutval(par);
+}
+
+static int 
+dfs_range(node_t* v, edge_t* par, int low)
+{
+       edge_t  *e;
+       int             i,lim;
+
+       lim = low;
+       ND_par(v) = par;
+       ND_low(v) = low;
+       for (i = 0; (e = ND_tree_out(v).list[i]); i++)
+               if (e != par) lim = dfs_range(aghead(e),e,lim);
+       for (i = 0; (e = ND_tree_in(v).list[i]); i++)
+               if (e != par) lim = dfs_range(agtail(e),e,lim);
+       ND_lim(v) = lim;
+       return lim + 1;
+}
+
+#ifdef DEBUG
+void tchk(void)
+{
+       int             i,n_cnt,e_cnt;
+       node_t  *n;
+       edge_t  *e;
+
+       n_cnt = 0;
+       e_cnt = 0;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               n_cnt++;
+               for (i = 0; (e = ND_tree_out(n).list[i]); i++) {
+                       e_cnt++;
+                       if (SLACK(e) > 0)
+                               printf("not a tight tree %p",(void*)e);
+               }
+       }
+       if ((n_cnt != Tree_node.size)  || (e_cnt != Tree_edge.size))
+               printf("something missing\n");
+}
+
+void check_cutvalues(void)
+{
+       node_t  *v;
+       edge_t  *e;
+       int             i,save;
+
+       for (v = agfstnode(G); v; v = agnxtnode(G,v)) {
+               for (i = 0; (e = ND_tree_out(v).list[i]); i++) {
+                       save = ED_cutval(e);
+                       x_cutval(e);
+                       if (save != ED_cutval(e)) abort();
+               }
+       }
+}
+
+int
+check_ranks(void)
+{
+       int             cost = 0;
+       node_t  *n;
+       edge_t  *e;
+
+       for (n = agfstnode(G); n; n = agnxtnode(G,n)) {
+               for (e = agfstout(G,n); e; e = agnxtout(G,e)) {
+                       cost += (ED_weight(e))*abs(LENGTH(e));
+       if (ND_rank(aghead(e)) - ND_rank(agtail(e)) - ED_minlen(e) < 0) abort();
+               }
+       }
+       fprintf(stderr,"rank cost %d\n",cost);
+       return cost;
+}
+
+void checktree(void)
+{
+       int             i,n = 0,m = 0;
+       node_t  *v;
+       edge_t  *e;
+
+       for (v = agfstnode(G); v; v = agnxtnode(G,v)) {
+               for (i = 0; (e = ND_tree_out(v).list[i]); i++) n++;
+               if (i != ND_tree_out(v).size) abort();
+               for (i = 0; (e = ND_tree_in(v).list[i]); i++) m++;
+               if (i != ND_tree_in(v).size) abort();
+       }
+       printf("%d %d %d\n",Tree_edge.size,n,m);
+}
+
+void checkdfs(node_t* n)
+{
+       edge_t  *e;
+       node_t  *w;
+
+       if (ND_mark(n)) return;
+       ND_mark(n) = TRUE;
+       ND_onstack(n) = TRUE;
+       for (e = agfstout(G,n); e; e = agnxtout(G,e)) {
+               w = aghead(e);
+               if (ND_onstack(w))
+                       fprintf(stderr,"cycle involving %s %s\n",agnameof(n),agnameof(w));
+               else {
+                       if (ND_mark(w) == FALSE) checkdfs(w);
+               }
+       }
+       ND_onstack(n) = FALSE;
+}
+
+void check_cycles(graph_t* g)
+{
+       node_t  *n;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n))
+               ND_mark(n) = ND_onstack(n) = FALSE;
+       for (n = agfstnode(G); n; n = agnxtnode(G,n))
+               checkdfs(n);
+}
+#endif /* DEBUG */
diff --git a/lib/dotgen2/pos.c b/lib/dotgen2/pos.c
new file mode 100644 (file)
index 0000000..f40fc06
--- /dev/null
@@ -0,0 +1,68 @@
+void dot_position(Agraph_t *model)
+{
+  make_variables();
+       node_separation_constraints();
+       cluster_containment_constraints();
+       edge_cost_constraints();
+}
+
+/* g is a model graph */
+static void node_separation_constraints(Agraph_t *g)
+{
+       rank_t  *r;
+
+
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
+               r = GD_rank(g)[i];
+               left = NILnode;
+               for (j = leftmost(g,r); j <= rightmost(g,r); j++) {
+                       right = r->v[j];
+                       node_sep(left,right);
+                       right = left;
+               }
+       }
+}
+
+/*
+ * this function has to traverse the subgraph hierarchy from the
+ * user graph because we flattened out the model subgraphs.
+ */
+static void cluster_containment_constraints(Agraph_t *upar, Agraph_t *ug)
+{
+       Agraph_t        *container;
+
+       if (is_a_cluster(upar) && is_a_cluster(ug))
+               cluster_contain(upar,ug);
+       container = is_a_cluster(ug)? ug : upar;
+       for (subg = agfstsub(ug); subg; subg = agnxtsubg(ug,subg))
+               cluster_containment_constraints(container,subg);
+}
+
+static void cluster_contain(Agraph_t *u_outer, Agraph_t *u_inner)
+{
+       Agraph_t        *Xg;
+       int             sep;
+       Agnode_t        *outer_left, *outer_right, *inner_left, *inner_right;
+
+       sep = user_cluster_sep(u_inner);
+       Xg = constraint_graph(u_outer);
+       outer_left = leftvar(GD_model(u_outer));
+       outer_right = rightvar(GD_model(u_outer));
+       inner_left = leftvar(GD_model(u_inner));
+       inner_right = rightvar(GD_model(u_inner));
+       clust_sep_edge(outer_left,inner_left);
+       clust_sep_edge(inner_right,outer_right);
+}
+
+static void clust_sep_edge(Agraph_t *Xg, Agnode_t *left, Agnode_t *right, int sep)
+{
+       Agedge_t        *e;
+
+       e = agedge(Xg, left, right);
+       ED_weight(e) = 0;
+       ED_minlen(e) = sep;
+}
+
+static void edge_cost_constraints()
+{
+}
diff --git a/lib/dotgen2/queue.c b/lib/dotgen2/queue.c
new file mode 100644 (file)
index 0000000..1eda3ca
--- /dev/null
@@ -0,0 +1,41 @@
+#include "newdot.h"
+
+/*
+ *  a queue of nodes
+ */
+queue *
+new_queue(int sz)
+{
+    queue       *q = NEW(queue);
+                                                                                
+    if (sz <= 1) sz = 2;
+    q->head = q->tail = q->store = N_NEW(sz,node_t*);
+    q->limit = q->store + sz;
+    return q;
+}
+                                                                                
+void
+free_queue(queue* q)
+{
+    free(q->store);
+    free(q);
+}
+                                                                                
+void
+enqueue(queue* q, node_t* n)
+{
+    *(q->tail++) = n;
+    if (q->tail >= q->limit) q->tail = q->store;
+}
+                                                                                
+node_t *
+dequeue(queue* q)
+{
+    node_t  *n;
+    if (q->head == q->tail) n = NULL;
+    else {
+        n = *(q->head++);
+        if (q->head >= q->limit) q->head = q->store;
+    }
+    return n;
+}
diff --git a/lib/dotgen2/radix.c b/lib/dotgen2/radix.c
new file mode 100644 (file)
index 0000000..3e8ee34
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+From: Andre Reinald <Andre.Reinald@vtcom.fr>
+To: submissive@cubic.org
+
+I though you might be intersted by some modifications I brought to your
+code in order to improve legibility and slightly speed.
+
+Well, my source is commented and I often wrote 4 lines where you had
+only one, but the generated code is smaller and it gives more
+opportunity to check intermediate steps while debugging.
+
+By the way, I did so on a PowerMac with CodeWarrior compiler, so your
+source is really portable.
+*/
+
+/* hacked by north to support a data pointer with each record */
+
+#include <stdlib.h>
+#include <assert.h>
+#include "radix.h"
+
+/* complicated expression better fits as macro (or inline in C++) */
+#define ByteOf(x) (((x) >> bitsOffset) & 0xff)
+
+typedef unsigned long ulong;
+typedef radixrec_t rec;
+
+/* replaced byte with bitsOffset to avoid *8 operation in loop */
+static void radix (short bitsOffset, ulong N, rec *source, rec *dest)
+{
+/* suppressed the need for index as it is reported in count */
+        ulong count[256];
+/* added temp variables to simplify writing, understanding and compiler optimization job */
+/* most of them will be allocated as registers */
+        ulong *cp, c, i, s;
+               rec       *sp, srec;
+
+/* faster than MemSet */
+        cp = count;
+        for (i = 256; i > 0; --i, ++cp)
+                *cp = 0;
+
+/* count occurences of every byte value */
+        sp = source;
+        for (i = N; i > 0; --i, ++sp) {
+                cp = count + ByteOf (sp->key);
+                ++(*cp);
+        }
+
+/* transform count into index by summing elements and storing into same array */
+        s = 0;
+        cp = count;
+        for (i = 256; i > 0; --i, ++cp) {
+                c = *cp;
+                *cp = s;
+                s += c;
+        }
+
+/* fill dest with the right values in the right place */
+        sp = source;
+        for (i = N; i > 0; --i, ++sp) {
+                srec = *sp;
+                cp = count + ByteOf (srec.key);
+                dest[*cp] = srec;
+                ++(*cp);
+        }
+}
+
+void radix_sort (rec *source, ulong N)
+{
+/* allocate heap memory to avoid the need of additional parameter */
+        rec *temp = malloc (N * sizeof (rec));
+        assert (temp != NULL);
+
+        radix (0, N, source, temp);
+        radix (8, N, temp, source);
+        radix (16, N, source, temp);
+        radix (24, N, temp, source);
+
+        free (temp);
+}
+
+#ifdef MAIN
+static void make_random (rec *data, ulong N)
+{
+        for ( ; N > 0; --N, ++data) {
+                data->key = rand () | (rand () << 16);
+                               data->data = (void*)((data->key)^0xdeadbeef);
+               }
+}
+
+static void check_order (rec *data, ulong N)
+{
+/* only signal errors if any (should not be) */
+        --N;
+        for ( ; N > 0; --N, ++data) {
+                assert (data[0].key <= data[1].key);
+                               assert (data->data == (void*)((data->key)^0xdeadbeef));
+               }
+}
+
+/* test for big number of elements */
+static void test_radix (ulong N)
+{
+        rec *data = malloc (N * sizeof (rec));
+        assert (data != NULL);
+
+        make_random (data, N);
+        radix_sort (data, N);
+        check_order (data, N);
+
+        free (data);
+}
+
+void main (void)
+{
+        test_radix (10000000);
+}
+#endif
diff --git a/lib/dotgen2/radix.h b/lib/dotgen2/radix.h
new file mode 100644 (file)
index 0000000..e39fae2
--- /dev/null
@@ -0,0 +1,6 @@
+typedef struct radixrec_s {
+       unsigned long   key;
+       void    *data;
+} radixrec_t;
+
+void radix_sort(radixrec_t *source, unsigned long N);
diff --git a/lib/dotgen2/radix0.c b/lib/dotgen2/radix0.c
new file mode 100644 (file)
index 0000000..3b4aa35
--- /dev/null
@@ -0,0 +1,95 @@
+#include <stdlib.h>
+#include <assert.h>
+
+// complicated expression better fits as macro (or inline in C++)
+#define ByteOf(x) (*(uchar *) (x))
+
+typedef unsigned long ulong;
+typedef unsigned char uchar;
+
+// replaced byte with bitsOffset to avoid *8 operation in loop
+static void radix (short byteOffset, ulong N, ulong *source, ulong *dest)
+{
+// suppressed the need for index as it is reported in count
+        ulong count[256];
+// added temp variables to simplify writing, understanding and compiler optimization job
+// most of them will be allocated as registers
+        ulong *sp, *cp, s, c, i;
+        uchar *bp;
+
+// faster than MemSet
+        cp = count;
+        for (i = 256; i > 0; --i, ++cp)
+                *cp = 0;
+
+// count occurences of every byte value
+        bp = ((uchar *) source) + byteOffset;
+        for (i = N; i > 0; --i, bp += 4) {
+                cp = count + *bp;
+                ++(*cp);
+        }
+
+// transform count into index by summing elements and storing into same array
+        s = 0;
+        cp = count;
+        for (i = 256; i > 0; --i, ++cp) {
+                c = *cp;
+                *cp = s;
+                s += c;
+        }
+
+// fill dest with the right values in the right place
+        bp = ((uchar *) source) + byteOffset;
+        sp = source;
+        for (i = N; i > 0; --i, bp += 4, ++sp) {
+                cp = count + *bp;
+                dest[*cp] = *sp;
+                ++(*cp);
+        }
+}
+
+static void radix_sort (ulong *source, ulong N)
+{
+// allocate heap memory to avoid the need of additional parameter
+        ulong *temp = (ulong *) malloc (N * sizeof (ulong));
+        assert (temp != NULL);
+
+        radix (3, N, source, temp);
+        radix (2, N, temp, source);
+        radix (1, N, source, temp);
+        radix (0, N, temp, source);
+
+        free (temp);
+}
+
+static void make_random (ulong *data, ulong N)
+{
+        for ( ; N > 0; --N, ++data)
+                *data = rand () | (rand () << 16);
+}
+
+static void check_order (ulong *data, ulong N)
+{
+// only signal errors if any (should not be)
+        --N;
+        for ( ; N > 0; --N, ++data)
+                assert (data[0] <= data[1]);
+}
+
+// test for big number of elements
+static void test_radix (ulong N)
+{
+        ulong *data = (ulong *) malloc (N * sizeof (ulong));
+        assert (data != NULL);
+
+        make_random (data, N);
+        radix_sort (data, N);
+        check_order (data, N);
+
+        free (data);
+}
+
+int main (int argc,  const char ** argv )
+{
+        test_radix (5000000);
+}
diff --git a/lib/dotgen2/radix1.c b/lib/dotgen2/radix1.c
new file mode 100644 (file)
index 0000000..911d20e
--- /dev/null
@@ -0,0 +1,105 @@
+/* 
+       http://www.cubic.org/~submissive/sourcerer/radix.htm
+       http://www.cubic.org/~submissive/sourcerer/download/radix_ar_2001.c
+*/
+
+#include <stdlib.h>
+#include <assert.h>
+
+// complicated expression better fits as macro (or inline in C++)
+#define ByteOf(x) (*(uchar *) (x))
+
+typedef unsigned long ulong;
+typedef unsigned char uchar;
+
+typedef struct rec_s {
+       ulong           key;
+} rec;
+
+// replaced byte with bitsOffset to avoid *8 operation in loop
+static void radix (short byteOffset, ulong N,  rec *source, rec *dest)
+{
+// suppressed the need for index as it is reported in count
+        ulong count[256];
+// added temp variables to simplify writing, understanding and compiler optimization job
+// most of them will be allocated as registers
+        ulong *cp, s, c, i;
+        uchar *bp;
+               rec   *sp;
+
+// faster than MemSet
+        cp = count;
+        for (i = 256; i > 0; --i, ++cp)
+                *cp = 0;
+
+// count occurences of every byte value
+        bp = ((uchar *) source) + byteOffset;
+        for (i = N; i > 0; --i, bp += sizeof(rec)) {
+                cp = count + *bp;
+                ++(*cp);
+        }
+
+// transform count into index by summing elements and storing into same array
+        s = 0;
+        cp = count;
+        for (i = 256; i > 0; --i, ++cp) {
+                c = *cp;
+                *cp = s;
+                s += c;
+        }
+
+// fill dest with the right values in the right place
+        bp = ((uchar *) source) + byteOffset;
+        sp = source;
+        for (i = N; i > 0; --i, bp += sizeof(rec), ++sp) {
+                cp = count + *bp;
+                dest[*cp] = *sp;
+                ++(*cp);
+        }
+}
+
+static void radix_sort (rec *source, ulong N)
+{
+// allocate heap memory to avoid the need of additional parameter
+        rec *temp = (rec *) malloc (N * sizeof (rec));
+        assert (temp != NULL);
+
+        radix (3, N, source, temp);
+        radix (2, N, temp, source);
+        radix (1, N, source, temp);
+        radix (0, N, temp, source);
+
+        free (temp);
+}
+
+static void make_random (rec *data, ulong N)
+{
+        for ( ; N > 0; --N, ++data)
+                data->key = rand () | (rand () << 16);
+}
+
+static void check_order (rec *data, ulong N)
+{
+// only signal errors if any (should not be)
+        --N;
+        for ( ; N > 0; --N, ++data)
+                assert (data[0].key <= data[1].key);
+}
+
+// test for big number of elements
+static void test_radix (ulong N)
+{
+        rec *data = (rec *) malloc (N * sizeof (rec));
+        assert (data != NULL);
+
+        make_random (data, N);
+        radix_sort (data, N);
+        check_order (data, N);
+
+        free (data);
+}
+
+int main (int argc, const char ** argv)
+{
+        test_radix (5000000);
+}
diff --git a/lib/dotgen2/save2_minc.c b/lib/dotgen2/save2_minc.c
new file mode 100644 (file)
index 0000000..93cb37e
--- /dev/null
@@ -0,0 +1,970 @@
+#include "newdot.h"
+
+/* given:
+ *             ND_rank(v)                      int                     integer level assignments
+ *             ND_ranksize(v)  int                     number of levels (>= 1)
+ *     ED_xpenalty(e)  float   crossing penalty factor
+ * find:
+ *             ND_pos(v)                               coord           
+ *             ED_pos(e)                               coord*
+ *
+ *             todo: graph, edge labels, flat edges
+ */
+
+/* internal graph variables:
+ * user graph
+ *       ND_cluster(v)         graph*  lowest containing cluster
+ *             GD_model(g)                     graph*  model graph
+ *             GD_parent(g)            graph*  parent cluster
+ *             GD_level                                int                     distance from layout root
+ *
+ * model graph objects
+ *             GD_usergraph(g) graph*          original graph or one of its clusters
+ *             GD_minrank(g)           int                             lowest rank index incl. external edges
+ *             GD_maxrank(g)           int                             highest rank index incl. external edges
+ *             GD_minlocalrank(g)              int     lowest rank index of internal nodes
+ *             GD_maxlocalrank(g)              int     highest rank index of internal nodes
+ *             GD_rank(g)                      rank_t[]
+ *             GD_repdict(g)           Dict_t*         local representatives of user objects
+ *             ND_component(v) int                             connected component number
+ *             ND_order(v)                     int                             order within rank
+ *             ND_cluster(v)           graph*          for EXTNODE, its (lowest) user cluster
+ */
+
+/* TODO
+       where do we use port.defined?
+ */
+
+/*** basic user<->model association maps ***/
+/* determine canonical order of n0, n1 */
+static void getlowhigh(Agnode_t **n0, Agnode_t **n1)
+{
+       Agnode_t        *temp;
+       int                             d;
+       d =  ND_rank(*n0) - ND_rank(*n1);
+       if ((d < 0) || ((d == 0) && ((*n0)->id > (*n1)->id)))
+               {temp = *n0; *n0 = *n1; *n1 = temp;}
+}
+
+static int repkeycmp(Dt_t *d, void *arg0, void *arg1, Dtdisc_t *disc)
+{
+       void *key0 = ((repkey_t*)arg0)->key;
+       void *key1 = ((repkey_t*)arg1)->key;
+       int             rv;
+       switch(agobjkind(key0)) {
+       case AGNODE:
+               rv = ((Agnode_t*)key0)->id - ((Agnode_t*)key1)->id;
+               break;
+       case AGEDGE:
+               rv = ((Agedge_t*)key0)->id - ((Agedge_t*)key1)->id;
+               break;
+       case AGRAPH:
+               /* in libgraph we don't seem to have graph ids, so use pointer */
+               rv = (unsigned long)key0 - (unsigned long) key1;
+               break;
+       default:
+               rv = 0;
+       }
+       return rv;
+}
+
+static Dtdisc_t Repdisc = {
+       0,                      /* pass whole object as key */
+       0,                      /* key size and type */
+       -1,                     /* link offset */
+       (Dtmake_f)0,
+       (Dtfree_f)0,
+       (Dtcompar_f) repkeycmp,
+       (Dthash_f)0,
+       (Dtmemory_f)0,
+       (Dtevent_f)0
+};
+
+static Dict_t *repdict(Agraph_t *model)
+{
+       Dict_t  *dict;
+
+       dict = GD_repdict(model);
+       if (!dict) dict = GD_repdict(model) = dtopen(&Repdisc,Dttree);
+       return dict;
+}
+
+static rep_t association(Agraph_t *model, void *obj)
+{
+       Dict_t *dict;
+       repkey_t        key, rv, *p;
+
+       dict = repdict(model);
+       key.key = obj;          /* i hate when other people code like this */
+       if ((p = dtsearch(dict,&key))) rv = *p;
+       else {rv.val.type = 0; rv.val.p = 0;}
+       return rv.val;
+}
+
+static void associate(Agraph_t *model, void *key, rep_t val)
+{
+       Dict_t          *dict;
+       repkey_t        *newelt;
+
+       assert(association(model,key).type == 0);
+       dict = repdict(model);
+       newelt = NEW(repkey_t);
+       newelt->key = key;
+       newelt->val = val;
+       dtinsert(dict,newelt);
+}
+
+typedef struct component_s {
+       int             n;
+       node_t  **root;
+       int r;
+} component_t;
+
+
+
+/* from level.c - eventually clean this up. */
+static int is_a_cluster(Agraph_t *g)
+{
+       return ((g == g->root) || (!strncasecmp(g->name,"cluster",7)));
+}
+
+/* find the cluster of n that is an immediate child of g */
+static Agraph_t *subclustof(Agnode_t *n, Agraph_t *g)
+{
+       Agraph_t                *rv;
+       for (rv = ND_cluster(n); rv && (GD_parent(rv) != g); rv = GD_parent(rv));
+       return rv;
+}
+
+static void *T_array(int low, int high, int size)
+{
+       char    *rv;
+
+       rv = calloc((high - low + 1),size);
+       rv = rv - (low * size);
+       return rv;
+}
+
+static Agnode_t **nodearray(int low, int high)
+{ return (Agnode_t**) T_array(low,high,sizeof(Agnode_t*)); }
+
+static Agedge_t **edgearray(int low, int high)
+{ return (Agedge_t**) T_array(low,high,sizeof(Agedge_t*)); }
+
+static int *intarray(int low, int high)
+{ return (int*) T_array(low,high,sizeof(int)); }
+
+/* internal functions for model graph construction and maintenance */
+static vpath_t *newpath(Agraph_t *model, Agnode_t *u, int low, Agnode_t *v, int high)
+{
+       vpath_t         *path;
+       int                             i;
+       path = NEW(vpath_t);
+       path->v = nodearray(low, high);
+       path->e = edgearray(low, high);
+       for (i = low; i <= high; i++) {
+               if ((i == low) && u) path->v[i] = u;
+               else if ((i == high) && v) path->v[i] = v;
+               else path->v[i] = agnode(model,0);
+               if (i > low) path->e[i-1] = agedge(model,path->v[i-1],path->v[i]);
+       }
+       return path;
+}
+
+/* a given edge can have several other edges (forward or backward)
+   between the same endpoints.  here, we choose one of these to be
+        the canonical representative of those edges. */
+static Agedge_t* canonical_edge(Agedge_t* e)
+{
+       Agraph_t        *g;
+       Agedge_t        *canon;
+
+       g = e->tail->graph;
+       if (ND_rank(e->head) > ND_rank(e->tail))
+               canon = agfindedge(g,e->tail,e->head);
+       else {
+               if 
+       }
+}
+
+static void model_edge(Agraph_t *model, Agedge_t *orig)
+{
+       Agedge_t        *e;
+       Agnode_t        *low, *high, *u, *v;
+       port_t          lowport, highport;
+       vpath_t         *path;
+       rep_t                   rep;
+       int                             i;
+
+       rep = association(model,orig);
+       if (rep.type == 0) {
+               low = orig->tail; high = orig->head;
+               getlowhigh(&low,&high);
+               u = association(model,low).p; assert(u);
+               v = association(model,high).p; assert(v);
+               path = newpath(model,u,ND_rank(low),v,ND_rank(high));
+               rep.type = PATH;
+               rep.p = path;
+               associate(model,orig,rep);
+       }
+       else path = rep.p;
+
+       /* merge the attributes of orig */
+       for (i = path->low; i < path->high; i++) {
+               e = path->e[i];
+               ED_xpenalty(e) += ED_xpenalty(orig);
+               ED_weight(e) += ED_weight(orig);
+       }
+
+       /* deal with ports.  note that ends could be swapped! */
+       if (ND_rank(orig->tail) <= ND_rank(orig->head)) {
+               lowport = ED_tailport(orig);
+               highport = ED_headport(orig);
+       }
+       else {
+               highport = ED_tailport(orig);
+               lowport = ED_headport(orig);
+       }
+       if (lowport.defined)
+               path->avgtailport = ((path->weight * path->avgtailport) + ED_weight(orig) * lowport.p.x) / (path->weight + ED_weight(orig));
+       if (highport.defined)
+               path->avgheadport = ((path->weight * path->avgheadport) + ED_weight(orig) * highport.p.x) / (path->weight + ED_weight(orig));
+       path->weight += ED_weight(orig);
+}
+
+/* bind/construct representative of an internal node in a model graph */
+static rep_t model_intnode(Agraph_t *model, Agnode_t *orig)
+{
+       int                             nr;
+       rep_t                   rep;
+       Agnode_t        *v;
+       
+       rep = association(model,orig);
+       if (rep.type) return rep;
+       
+       nr = ND_ranksize(orig);
+       if (nr <= 1) {                  /* simple case */
+               v = rep.p = agnode(model,orig->name);
+               rep.type = NODE;
+               ND_rank(v) = ND_rank(orig);
+       }
+       else {                                                  /* multi-rank node case */
+               rep.p = newpath(model,NILnode,ND_rank(orig),NILnode,ND_rank(orig)+nr-1);
+               rep.type = TALLNODE;
+       }
+       associate(model,orig,rep);
+       return rep;
+}
+
+/* bind/construct representative of an external endpoint to a model graph */
+static rep_t model_extnode(Agraph_t *model, Agnode_t *orig)
+{
+       Agnode_t        *v;
+       rep_t           rep;
+
+       rep = association(model,orig);
+       if (rep.type) return rep;
+
+       /* assume endpoint is represented by one node, even if orig is multi-rank */
+       rep.p = v = agnode(model,orig->name);
+       rep.type = EXTNODE;
+       ND_rank(v) = ND_rank(orig);     /* should be ND_rank(orig)+ranksize(orig)? */
+       associate(model,orig,rep);
+       return rep;
+}
+
+/* bind/construct representative of an internal cluster of a model graph */
+static rep_t model_clust(Agraph_t *model, Agraph_t *origclust)
+{
+       rep_t           rep;
+
+       rep = association(model,origclust);
+       if (rep.type) return rep;
+
+       rep.p = newpath(model,NILnode,GD_minrank(origclust),NILnode,GD_maxrank(origclust));
+       rep.type = SKELETON;
+       associate(model,origclust,rep);
+       return rep;
+}
+
+#ifdef NOTDEF
+       /* i think we ditched this because we assume nodes are always
+       built before edges */
+/* helper functions for model_edge */
+static rep_t bindnode(Agraph_t *model, Agnode_t *orignode)
+{
+       rep_t   rep;
+       if (agcontains(GD_originalgraph(model),orignode))
+               rep = model_intnode(model,orignode);
+       else
+               rep = model_extnode(model,orignode);
+       return rep;
+}
+#endif
+
+static int leftmost(Agraph_t *model, int r) { return 0; }
+static int rightmost(Agraph_t *model, int r) {return GD_rank(model)[r].n - 1;}
+
+static void flat_edges(Agraph_t *clust)
+{
+#ifdef NOTDEF
+       for (n = agfstnode(clust); n; n = agnxtnode(clust)) {
+               for (e = agfstedge(root,n); e; e = agnxtedge(root,e,n)) {
+               }
+       }
+       ordered_edges();
+#endif
+}
+
+static void search_component(Agraph_t *g, Agnode_t *n, int c)
+{
+       Agedge_t        *e;
+       ND_component(n) = c;
+       for (e = agfstout(g,n); e; e = agnxtout(g,e))
+               if (ND_component(e->head) < 0)
+                       search_component(g,e->head,c);
+       for (e = agfstin(g,n); e; e = agnxtin(g,e))
+               if (ND_component(e->tail) < 0)
+                       search_component(g,e->tail,c);
+}
+
+static int ND_comp_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_component(*(Agnode_t**)arg0) - ND_component(*(Agnode_t**)arg1);
+}
+
+static component_t build_components(Agraph_t *g, boolean down)
+{
+       component_t     rv;
+       node_t  *n;
+       int             r, rootcnt, compcnt, deg;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_component(n) = -1;   /* initialize to unknown component */
+
+       compcnt = 0; rootcnt = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) { 
+               /* set priority for subsequent BFS to install nodes, and record roots */
+               if (down) deg = ND_indeg(n);
+               else deg = ND_outdeg(n);
+               ND_priority(n) = deg;
+               if (deg == 0) rootcnt++;
+               /* count and mark components */
+               if (ND_component(n) < 0) search_component(g,n,compcnt++);
+       }
+
+       rv.n = compcnt;
+       rv.r = rootcnt;
+       rv.root = N_NEW(rv.r,Agnode_t*);
+       r = 0;
+       /* install roots in root list */
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               if (ND_priority(n) == 0) rv.root[r++] = n;
+       /* sort root list so components are contiguous */
+       qsort(rv.root,rv.n,sizeof(node_t*),ND_comp_cmpf);
+       return rv;
+}
+
+static void install(Agraph_t *g, Agnode_t *n)
+{
+       int                             rank;
+       rank_t          *r;
+
+       rank = ND_rank(n);
+       r = &GD_rank(g)[rank];
+       r->v[r->n] = n;
+       ND_order(n) = r->n++;
+}
+
+/* 
+ populates rank lists of g.  there are some key details:
+ 1) the input graph ordering must be respected (in left to right initialization)
+ 2) connected components are separated and marked with indices
+ 3) series-parallel graphs (includes trees, obviously) must not have crossings
+*/
+static void build_ranks(Agraph_t *g, boolean down)
+{
+       queue                   *q;
+       component_t c;
+       int                             r;
+       Agnode_t        *n;
+       Agedge_t        *e;
+
+       c = build_components(g, down);
+
+       /* process each each component */
+       q = new_queue(agnnodes(g)+1);
+       for (r = 0; r < c.r; r++) {
+               enqueue(q,c.root[r]);
+               if ((r + 1 >= c.r)||(ND_component(c.root[r])!=ND_component(c.root[r+1]))) {
+                       while ((n = dequeue(q))) {
+                               install(g,n);
+                                       if (down) {
+                                               for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                                                       if (--ND_priority(e->head) == 0) enqueue(q,e->head);
+                                       }
+                                       else {
+                                               for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                                                       if (--ND_priority(e->tail) == 0) enqueue(q,e->head);
+                                       }
+                       }
+               }
+       }
+       free_queue(q);
+}
+
+/* this predicate indicates if mincross should be run on this cluster */
+static boolean run(Agraph_t *mg)
+{
+       if (GD_pass(mg) > GD_maxpass(mg)) return FALSE;
+       if (GD_pass(mg) - GD_lastwin(mg) > GD_mintry(mg)) return FALSE;
+       GD_pass(mg) = GD_pass(mg) + 1;
+       return TRUE;
+}
+
+static int presort_cmpf(const void *arg0, const void *arg1)
+{
+       Agnode_t        *n0, *n1;
+       Agraph_t        *c0, *c1;
+
+       n0 = *(Agnode_t**)arg0;
+       n1 = *(Agnode_t**)arg1;
+       c0 = ND_cluster(n0);
+       c1 = ND_cluster(n1);
+       if (c0 == c1) return 0;
+       assert(ND_rank(n0) == ND_rank(n1));
+       n0 = GD_skel(c0)->v[ND_rank(n0)];
+       n1 = GD_skel(c1)->v[ND_rank(n1)];
+       return ND_order(n0) - ND_order(n1);
+}
+
+static void presort(Agraph_t *ug)
+{
+       int             r;
+       int             i;
+       Agraph_t        *mg;
+
+       if (ug == ug->root) return;
+       mg = GD_model(ug);
+       for (r = GD_minrank(mg); r <= GD_maxrank(mg); r++) {
+               qsort(GD_rank(mg)[r].v,GD_rank(mg)[r].n,sizeof(Agnode_t*),presort_cmpf);
+               for (i = leftmost(mg,r); i < rightmost(mg,r); i++)
+                       ND_order(GD_rank(mg)[r].v[i]) = i;
+       }
+}
+
+/* sets ports that represent connections to subclusters */
+static void subclustports(Agraph_t *ug)
+{
+       Agraph_t        *model, *clustmodel;
+       Agnode_t        *x;
+       Agedge_t        *e;
+       vpath_t         *p;
+       Dict_t          *d;
+       double          frac;
+
+       /* walk all the paths */
+       model = GD_model(ug);
+       d = repdict(model);
+       for (p = dtfirst(d); p; p = dtnext(d,p)) {
+               if ((ND_type(p->v[p->low])) == NODETYPE_CNODE) {
+                       x = p->v[p->low];
+                       clustmodel = GD_model(ND_cluster(x));
+                       frac = (ND_order(x) + 1) / GD_rank(clustmodel)[ND_rank(x)].n;
+                       e = p->e[p->low];
+                       ED_tailport(e).p.x = 2 * PORTCLAMP * (frac - 0.5) + p->tailport;
+               }
+               if ((ND_type(p->v[p->high])) == NODETYPE_CNODE) {
+                       x = p->v[p->high];
+                       clustmodel = GD_model(ND_cluster(x));
+                       frac = (ND_order(x) + 1) / GD_rank(clustmodel)[ND_rank(x)].n;
+                       e = p->e[p->high-1];
+                       ED_headport(e).p.x = 2 * PORTCLAMP * (frac - 0.5) + p->headport;
+               }
+       }
+}
+
+static void mincross_clust(Agraph_t *ug)
+{
+       Agraph_t        *g;
+       g = GD_model(ug);
+       if (run(g)) {
+               presort(ug);            /* move the external nodes */
+               subclustports(ug);
+               do {
+                       mincross_sweep(g,GD_pass(g)%2,GD_pass(g)%4<2);
+               } while (run(g));
+               transpose_sweep(g,TRUE);
+               restorebest(g);
+       }
+}
+
+static void globalopt(Agraph_t *root)
+{
+       Agraph_t        *g;
+       rank_t          *glob;
+
+       g = GD_model(root);
+       glob = globalize(root,g);
+       GD_rank(g) = glob;
+       fprintf(stderr,"%s: %d crossings\n",root->name,crossings(g));
+}
+
+/* this assumes one level per node - no mega-nodes */
+static void apply_model(Agraph_t *ug)
+{
+       Agnode_t *un;
+       for (un = agfstnode(ug); un; un = agnxtnode(ug,un)) 
+                       ND_order(un) = ND_order(ND_rep(un));
+}
+
+/* this is a first cut at a top-level planner.  it's lame. */
+static void rec_cluster_run(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) mincross_clust(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_run(subg);
+       if (is_a_cluster(ug)) mincross_clust(ug);
+}
+
+/* this is the top level mincross entry point */
+void dot_mincross(Agraph_t *user)
+{
+       rec_cluster_init(user);
+       rec_cluster_run(user);
+       globalopt(user);
+       apply_model(user);
+}
+
+static void invalidate(Agraph_t *g, int rank)
+{
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].crossing_cache.valid = FALSE;
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].candidate = TRUE;
+       if (rank < GD_maxrank(g)) GD_rank(g)[rank+1].candidate = TRUE;
+}
+
+/* swaps two nodes in the same level */
+static void exchange(Agraph_t *g, Agnode_t *u, Agnode_t *v)
+{
+       rank_t  *r;
+       int                     ui,vi,rank;
+
+       assert(ND_rank(u) == ND_rank(v));
+       rank = ND_rank(u);
+       r = &GD_rank(g)[rank];
+       ui = ND_order(u);
+       vi = ND_order(v);
+       ND_order(v) = ui;
+       ND_order(u) = vi;
+       r->v[ND_order(u)] = u;
+       r->v[ND_order(v)] = v;
+       r->crossing_cache.valid = FALSE;
+       r->changed = TRUE;
+       r->candidate = TRUE;    /* old dot had this.  i have qualms. sn */
+       invalidate(g,rank);
+}
+
+int transpose_onerank(Agraph_t* g, int r, boolean reverse)
+{
+       int     i,c0,c1,rv;
+       node_t  *v,*w;
+
+       rv = 0;
+       GD_rank(g)[r].candidate = FALSE;
+       for (i = leftmost(g,r); i < rightmost(g,r); i++) {
+               v = GD_rank(g)[r].v[i];
+               w = GD_rank(g)[r].v[i+1];
+               assert (ND_order(v) < ND_order(w));
+               if (left2right(g,v,w)) continue;
+               c0 = c1 = 0;
+               if (r > GD_minrank(g)) {
+                       c0 += in_cross(v,w);
+                       c1 += in_cross(w,v);
+               }
+               if (r < GD_maxrank(g)) {
+                       c0 += out_cross(v,w);
+                       c1 += out_cross(w,v);
+               }
+               if ((c1 < c0) || ((c0 > 0) && reverse && (c1 == c0))) {
+                       exchange(g,v,w);
+                       rv += (c0 - c1);
+               }
+       }
+       return rv;
+}
+
+static void transpose_sweep(Agraph_t* g, int reverse)
+{
+    int     r,delta;
+
+       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+               GD_rank(g)[r].candidate = TRUE;
+    do {
+                       delta = 0;
+                       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+                               if (GD_rank(g)[r].candidate) delta += transpose_onerank(g,r,reverse);
+    }
+               while (delta >= 1);
+               /* while (delta > crossings(g)*(1.0 - Convergence));*/
+}
+
+static void mincross_sweep(Agraph_t* g, int dir, boolean reverse)
+{
+    int     r,other,low,high,first,last;
+    int     hasfixed;
+
+               low = GD_minrank(g);
+               high = GD_maxrank(g);
+               if (dir == 0) return;
+               if (dir > 0)  { first = low + 1; last = high; dir = 1;}         /* down */
+               else                              { first = high - 1; last = low; dir = -1;}    /* up */
+
+    for (r = first; r != last + dir; r += dir) {
+        other = r - dir;
+        hasfixed = medians(g,r,other);
+        reorder(g,r,reverse,hasfixed);
+    }
+    transpose_sweep(g,NOT(reverse));
+       savebest(g);
+}
+
+
+static int left2right(Agraph_t *g, node_t *v, node_t *w)
+{
+    int         rv;
+
+#ifdef NOTDEF
+    adjmatrix_t *M;
+    M = GD_rank(g)[ND_rank(v)].flat;
+    if (M == NULL) rv = FALSE;
+    else {
+        if (GD_flip(g)) {node_t *t = v; v = w; w = t;}
+        rv = ELT(M,flatindex(v),flatindex(w));
+    }
+#else
+               rv = FALSE;
+#endif
+    return rv;
+}
+
+static void build_flat_graphs(Agraph_t *g)
+{
+}
+
+static int out_cross(node_t *v, node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int  inv, cross = 0,t;
+
+  for (e2 = agfstout(w->graph,w); e2; e2 = agnxtout(w->graph,e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(e2->head);
+               for (e1 = agfstout(v->graph,v); e1; e1 = agnxtout(v->graph,e1)) {
+                       t = ND_order(e1->head) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_headport(e1).p.x > ED_headport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+                                                                                
+static int in_cross(node_t *v,node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int inv, cross = 0, t;
+                                                                                
+  for (e2 = agfstin(w->graph,w); e2; e2 = agnxtin(w->graph,e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(e2->tail);
+               for (e1 = agfstin(v->graph,v); e1; e1 = agnxtin(v->graph,e1)) {
+                       t = ND_order(e1->tail) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_tailport(e1).p.x > ED_tailport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+
+static int int_cmpf(const void *arg0, const void *arg1)
+{
+       return *(int*)arg0 - *(int*)arg1;
+}
+
+
+/* 8 is the number of bits in port.order, an unsigned char */
+#define VAL(node,port) (((node)->u.order << 8)  + (port).order)
+
+/*
+ * defines ND_sortweight of each node in r0 w.r.t. r1
+ * returns...
+ */
+static boolean medians(Agraph_t *g, int r0, int r1)
+{
+       static int *list;
+       static int list_extent;
+       int     i,j,lm,rm,lspan,rspan;
+       node_t  *n,**v;
+       edge_t  *e;
+       boolean hasfixed = FALSE;
+
+       if (list_extent < GD_maxinoutdeg(g->root)) {
+               list_extent = GD_maxinoutdeg(g->root);
+               if (!list) list = realloc(list,sizeof(list[0])*list_extent);
+               else list = realloc(list,sizeof(list[0])*list_extent);
+       }
+       v = GD_rank(g)[r0].v;
+       for (i = leftmost(g,r0); i <= rightmost(g,r0); i++) {
+               n = v[i]; j = 0;
+               if (r1 > r0) for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(e->head,ED_headport(e));}
+               else for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(e->tail,ED_tailport(e));}
+               switch(j) {
+                       case 0:
+                               ND_sortweight(n) = -1;          /* no neighbor - median undefined */
+                               break;
+                       case 1:
+                               ND_sortweight(n) = list[0];
+                               break;
+                       case 2:
+                               ND_sortweight(n) = (list[0] + list[1])/2;
+                               break;
+                       default:
+                               qsort(list,j,sizeof(int),int_cmpf);
+                               if (j % 2) ND_sortweight(n) = list[j/2];
+                               else {
+                                       /* weighted median */
+                                       rm = j/2;
+                                       lm = rm - 1;
+                                       rspan = list[j-1] - list[rm];
+                                       lspan = list[lm] - list[0];
+                                       if (lspan == rspan)
+                                               ND_sortweight(n) = (list[lm] + list[rm])/2;
+                                       else {
+                                               int w = list[lm]*rspan + list[rm]*lspan;
+                                               ND_sortweight(n) = w / (lspan + rspan);
+                                       }
+                               }
+               }
+       }
+#ifdef NOTDEF
+       /* this code was in the old mincross */
+       for (i = 0; i < GD_rank(g)[r0].n; i++) {
+               n = v[i];
+               if ((ND_out(n).size == 0) && (ND_in(n).size == 0))
+                       hasfixed |= flat_sortweight(n);
+       }
+#endif
+       return hasfixed;
+}
+
+static void reorder(graph_t *g, int r, boolean reverse, boolean hasfixed)
+{
+       boolean changed, muststay;
+       node_t  **vlist, **lp, **rp, **ep;
+       int                     i;
+
+       changed = FALSE;
+       vlist = GD_rank(g)[r].v;
+       ep = &vlist[rightmost(g,r)];
+       
+       for (i = leftmost(g,r); i <= rightmost(g,r); i++) {
+               lp = &vlist[leftmost(g,r)];
+               /* find leftmost node that can be compared */
+               while ((lp < ep) && (ND_sortweight(*lp) < 0)) lp++;
+               if (lp >= ep) break;
+               /* find the node that can be compared */
+               muststay = FALSE;
+               for (rp = lp + 1; rp < ep; rp++) {
+                       if (left2right(g,*lp,*rp)) { muststay = TRUE; break; }
+                       if (ND_sortweight(*rp) >= 0) break;     /* weight defined; it's comparable */
+               }
+               if (rp >= ep) break;
+               if (muststay == FALSE) {
+                       register int    p1 = ND_sortweight(*lp);
+                       register int    p2 = ND_sortweight(*rp);
+                       if ((p1 > p2) || ((p1 == p2) && (reverse))) {
+                               exchange(g,*lp,*rp);
+                               changed = TRUE;
+                       }
+               }
+               lp = rp;
+               if ((hasfixed == FALSE) && (reverse == FALSE)) ep--;
+       }
+                                                                                
+       if (changed) {
+               GD_rank(g)[r].changed = TRUE;
+               GD_rank(g)[r].crossing_cache.valid = FALSE;
+               if (r > 0) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+               if (r + 1 > GD_rank(g)[r+1].n) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+       }
+}
+
+static void savebest(graph_t *g)
+{
+       int             nc;
+       Agnode_t        *n;
+
+       nc = crossings(g);
+       if (nc < GD_bestcrossings(g)) {
+               for (n = agfstnode(g); n; n = agnxtnode(g,n))
+                       ND_saveorder(n) = ND_order(n);
+               GD_bestcrossings(g) = nc;
+               GD_lastwin(g) = GD_pass(g);
+       }
+}
+
+static int ND_order_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_order(*(Agnode_t**)arg0) - ND_order(*(Agnode_t**)arg1);
+}
+
+static void restorebest(graph_t *g)
+{
+       Agnode_t        *n;
+       int                     i;
+
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++) 
+               GD_rank(g)[i].changed = FALSE;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))  {
+               if (ND_order(n) != ND_saveorder(n)) {
+                       invalidate(g,ND_rank(n));
+                       GD_rank(g)[i].changed = TRUE;
+                       ND_order(n) = ND_saveorder(n);
+               }
+       }
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++)  {
+               if (GD_rank(g)[i].changed)
+                       qsort(GD_rank(g)[i].v,GD_rank(g)[i].n,sizeof(Agnode_t*),ND_order_cmpf);
+       }
+}
+
+static int crossings(graph_t *g)
+{
+               int             i, rv;
+
+               rv = 0;
+               for (i = GD_minrank(g); i < GD_maxrank(g); i++) {
+                       rv += crossings_below(g,i);
+               }
+               return rv;
+}
+
+/* build the global (flat) graph of the universe.  this is 'flat'
+in the sense that there is one data structure for the entire graph
+(not 'flat' in the sense of flat edges within the same level.)
+*/
+static rank_t *globalize(Agraph_t *user, Agraph_t *topmodel)
+{
+       rank_t  *globrank;
+       int             minr,maxr,r;
+
+       /* setup bookkeeping */
+       interclusterpaths(user, topmodel);
+
+       /* allocate global ranks */
+       minr = GD_minrank(topmodel);
+       maxr = GD_maxrank(topmodel);
+       globrank = T_array(minr,maxr,sizeof(rank_t));
+       countup(user,globrank);
+       for (r = minr; r <= maxr; r++) {
+               globrank[r].v = N_NEW(globrank[r].n+1,Agnode_t*);       /* NIL at end */
+               globrank[r].n = 0;      /* reset it */
+       }
+
+       /* installation */
+       for (r = minr; r <= maxr; r++)
+               installglob(user,topmodel,globrank,r);
+
+       removejunk(user, topmodel);
+       reconnect(user, topmodel);
+
+       /* optimization */
+       return globrank;
+}
+
+static void countup(Agraph_t *g, rank_t *globr)
+{
+       Agnode_t        *n;
+       Agedge_t        *e;
+       int                             r0, r1, low, high, i;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+                       for (i = 0; i < ND_ranksize(n); i++)
+                               globr[ND_rank(n)+i].n += 1;
+                       for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
+                               r0 = ND_rank(e->tail);
+                               r1 = ND_rank(e->head);
+                               low = MIN(r0,r1);
+                               high = MAX(r0,r1);
+                               for (i = low + 1; i < high; i++)
+                                       globr[i].n += 1;
+                       }
+       }
+}
+
+static void rec_model_subclusts(Agraph_t *model, Agraph_t *user)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(user))
+               (void) model_clust(model,user);
+       /* note there can be non-cluster subgraphs that contain lower clusters */
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_model_subclusts(model,subg);
+}
+
+static void cluster_contents(Agraph_t *ug)
+{
+       Agraph_t        *mg;
+       char *name;
+
+       assert(is_a_cluster(ug));
+       assert(GD_model(ug) == 0);
+
+       name = malloc(strlen(ug->name)+10);
+       sprintf(name,"model_%s",ug->name);
+       GD_model(ug) = mg = agopen(name,AGDIGRAPHSTRICT);
+       free(name);
+
+       /* install internal nodes */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               if (ND_cluster(n) == ug) model_intnode(mg,n);
+       }
+
+       /* install cluster skeletons */
+       rec_model_subclusts(mg,ug);
+
+       /* install external edge endpoints */
+       root = GD_layoutroot(ug);
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(root,n); e; e = agnxtout(root,e)) {
+                       if (!agcontains(ug,e->tail))
+                               model_extnode(mg,e->tail);
+                       if (!agcontains(ug,e->head))
+                               model_extnode(mg,e->head);
+               }
+       }
+
+       /* install edges */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n))
+               for (e = agfstout(root,n); e; e = agnxtout(root,e))
+                       model_edge(mg,e);               /* amazing if this is all it takes */
+}
+
+static void cluster_init(Agraph_t *ug)
+{
+       cluster_contents(ug);
+       flat_edges(ug);
+}
+
+static void rec_cluster_init(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) cluster_init(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_init(subg);
+}
diff --git a/lib/dotgen2/save_minc.c b/lib/dotgen2/save_minc.c
new file mode 100644 (file)
index 0000000..5902c3f
--- /dev/null
@@ -0,0 +1,1281 @@
+#include "newdot.h"
+
+/* given:
+ *             ND_level(v)             int                     integer level assignments
+ *             ND_something(v) int                     number of levels (if > 1)
+ *     ED_xpenalty(e)  float   crossing penalty factor
+ * find:
+ *             ND_pos(v)                               coord           
+ *             ED_pos(e)                               coord*
+ *
+ *             todo: graph, edge labels
+ */
+
+/* computed values:
+ * user graph
+ *       ND_cluster(v)         graph*  lowest containing cluster
+ *             GD_model(g)                     graph*  model graph
+ *             GD_parent(g)            graph*  parent cluster
+ *
+ * model graph objects
+ *             GD_minrank(g)           int
+ *             GD_maxrank(g)           int
+ *             GD_rank(g)                      rank_t[]
+ *             GD_skel(g)                      vpath_t*        path representing g in its parent
+ *             ND_component(v) int                             connected component number
+ */
+
+ * ND_vrep(user node) <-> ND_vrep(model node : REAL)
+ * ND_erep(model node : XNODE)  == user edge
+ * ND_erep(model node : VNODE)  == user edge
+ * ND_grep(model node : CNODE) == user cluster
+ */
+
+/* TODO
+       where do we use port.defined?
+ */
+
+typedef struct component_s {
+       int             n;
+       node_t  **root;
+       int r;
+} component_t;
+
+static Agraph_t *subclustof(Agnode_t *n, Agraph_t *clust);
+static void *T_array(int low, int high, int size);
+static Agnode_t **nodearray(int low, int high);
+static int *intarray(int low, int high);
+static void cluster_model(Agraph_t *clust);
+static void flat_edges(Agraph_t *clust);
+static void search_component(Agraph_t *g, Agnode_t *n, int c);
+static int ND_comp_cmpf(const void *arg0, const void *arg1);
+static component_t build_components(Agraph_t *g, boolean down);
+static void install(Agraph_t *g, Agnode_t *n);
+static void build_ranks(Agraph_t *ug, boolean down);
+static void rec_cluster_init(Agraph_t *userclust);
+static boolean run(Agraph_t *g);
+static void mincross_clust(Agraph_t *clust);
+static void globalopt(Agraph_t *ug);
+static void rec_cluster_run(Agraph_t *ug);
+static void exchange(Agraph_t *g, Agnode_t *u, Agnode_t *v);
+static void transpose_sweep(Agraph_t* g, int reverse);
+static void mincross_sweep(Agraph_t* g, int dir, boolean reverse);
+static int left2right(Agraph_t *g, node_t *v, node_t *w);
+static void build_flat_graphs(Agraph_t *g);
+static boolean medians(Agraph_t *g, int r0, int r1);
+static void reorder(Agraph_t *g, int r, boolean reverse, boolean hasfixed);
+static void savebest(Agraph_t *g);
+static void restorebest(Agraph_t *g);
+static int crossings(Agraph_t *g);
+static int in_cross(node_t *v,node_t *w);
+static int out_cross(node_t *v,node_t *w);
+static rank_t *globalize(Agraph_t *user, Agraph_t *model);
+static void installglob(Agraph_t *user, Agraph_t *fromgraph, rank_t *globr, int r);
+static void countup(Agraph_t *g, rank_t *globr);
+
+static Agnode_t *model_getrep(Agraph_t *ug, Agnode_t *n, Agedge_t *e);
+static Agraph_t *universegraph(Agraph_t *ug);
+static vpath_t *model_skel(Agraph_t *uclust, Agraph_t *model);
+static void removejunk(Agraph_t *ug, Agraph_t *topmodel);
+static void reconnect(Agraph_t *ug, Agraph_t *topmodel);
+
+/* from level.c - eventually clean this up. */
+static int is_a_cluster(Agraph_t *g)
+{
+       return ((g == g->root) || (!strncasecmp(g->name,"cluster",7)));
+}
+
+/* find the cluster of n that is an immediate child of g */
+static Agraph_t *subclustof(Agnode_t *n, Agraph_t *g)
+{
+       Agraph_t                *rv;
+       for (rv = ND_cluster(n); rv && (GD_parent(rv) != g); rv = GD_parent(rv));
+       return rv;
+}
+
+static void *T_array(int low, int high, int size)
+{
+       char    *rv;
+
+       rv = calloc((high - low + 1),size);
+       rv = rv - (low * size);
+       return rv;
+}
+
+static Agnode_t **nodearray(int low, int high)
+{ return (Agnode_t**) T_array(low,high,sizeof(Agnode_t*)); }
+
+static Agedge_t **edgearray(int low, int high)
+{ return (Agedge_t**) T_array(low,high,sizeof(Agedge_t*)); }
+
+static int *intarray(int low, int high)
+{ return (int*) T_array(low,high,sizeof(int)); }
+
+static Agnode_t *model_realnode(Agraph_t *modelgraph, Agnode_t *orig)
+{
+       Agnode_t        *rep;
+       /* this is OK because realnode can only be in one model graph/subgraph */
+       rep = agnode(modelgraph,orig->name);
+       ND_rank(rep) = ND_rank(orig);
+       ND_cluster(rep) = modelgraph;
+       ND_vrep(orig) = rep;
+       ND_vrep(rep) = orig;
+       ND_type(rep) = NODETYPE_REAL;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agnode_t *model_extnode(Agraph_t *modelgraph, Agnode_t *orig, Agedge_t *ue)
+{
+       char name[1024];
+       Agnode_t        *rep;
+
+       sprintf(name,"ext_%s_%s",modelgraph->name,orig->name);
+       /* can't be anonymous - needs to be repeatable */
+       rep = agnode(modelgraph,name);
+       ND_rank(rep) = ND_rank(orig);
+       ND_cluster(rep) = modelgraph;
+       ND_erep(rep) = ue;
+       ND_type(rep) = NODETYPE_XNODE;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agnode_t *model_vnode(Agraph_t *modelgraph, Agedge_t *uedge, int level)
+{
+       Agnode_t        *rep;
+       rep = agnode(modelgraph,0);
+       ND_rank(rep) = level;
+       ND_cluster(rep) = modelgraph;
+       ND_erep(rep) = uedge;           /* this may be bogus, what if multiple? */
+       ND_type(rep) = NODETYPE_VNODE;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agnode_t *model_cnode(Agraph_t *modelgraph, Agraph_t *uclust, int level)
+{
+       Agnode_t        *rep;
+       rep = agnode(modelgraph,0);
+       ND_rank(rep) = level;
+       ND_cluster(rep) = uclust;
+       ND_grep(rep) = uclust;
+       ND_type(rep) = NODETYPE_CNODE;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agedge_t *model_vedge(Agraph_t *modelgraph, Agnode_t *tx, Agnode_t *hx, Agedge_t *orig)
+{
+       Agedge_t        *e;
+
+       e = agedge(modelgraph,tx,hx);
+       ED_xpenalty(e) = VEDGE_PENALTY * ED_xpenalty(orig);
+       ED_weight(e) = ED_weight(orig);
+       return e;
+}
+
+static Agedge_t *model_cedge(Agraph_t *modelgraph, Agnode_t *tx, Agnode_t *hx)
+{
+       Agedge_t        *e;
+
+       e = agedge(modelgraph,tx,hx);
+       ED_xpenalty(e) = CEDGE_PENALTY;
+       ED_weight(e) = 1;
+       return e;
+}
+
+static void getlowhigh(Agnode_t **n0, Agnode_t **n1)
+{
+       Agnode_t        *temp;
+       if (ND_rank(*n0) > ND_rank(*n1))
+               {temp = *n0; *n0 = *n1; *n1 = temp;}
+}
+
+vpath_t *newpath(Agnode_t *u, Agnode_t *v)
+{
+       Agnode_t        *low = u, *high = v;
+       vpath_t         *p;
+
+       getlowhigh(&low,&high);
+       p = NEW(vpath_t);
+       p->key.tail = low;
+       p->key.head = high;
+       p->low = ND_rank(low);
+       p->high = ND_rank(high);
+       p->v = nodearray(p->low, p->high);
+       p->v[p->low] = low;
+       p->v[p->high] = high;
+       p->e = edgearray(p->low, p->high);
+       return p;
+}
+
+static int pathcmp(Dt_t *d, void *arg0, void *arg1, Dtdisc_t *disc)
+{
+       int             rv;
+       if ((rv = ((vpath_t*)arg0)->key.tail - ((vpath_t*)arg1)->key.tail) == 0)
+               rv = ((vpath_t*)arg0)->key.head - ((vpath_t*)arg1)->key.head;
+       return rv;
+}
+
+static Dtdisc_t Vpathdisc = {
+       0,                      /* pass whole object as key */
+       0,                      /* key size and type */
+       -1,                     /* link offset */
+       (Dtmake_f)0,
+       (Dtfree_f)0,
+       (Dtcompar_f) pathcmp,
+       (Dthash_f)0,
+       (Dtmemory_f)0,
+       (Dtevent_f)0
+};
+
+static Dict_t *pathdict(Agraph_t *model)
+{
+       Dict_t  *dict;
+
+       dict = GD_pathdict(model);
+       if (!dict) dict = GD_pathdict(model) = dtopen(&Vpathdisc,Dttree);
+       return dict;
+}
+
+static void pathinsert(Agraph_t *model, vpath_t *p)
+{
+       Dict_t  *dict;
+       dict = pathdict(model);
+       p = dtinsert(dict,p);
+}
+
+static vpath_t *pathsearch(Agraph_t *modelgraph, Agnode_t *origlow, Agnode_t *orighigh)
+{
+       Dict_t *dict;
+       vpath_t path, *p;
+
+       dict = pathdict(modelgraph);
+       path.key.tail = origlow;
+       path.key.head = orighigh;
+       p = dtsearch(dict,&path);
+       return p;
+
+}
+
+vpath_t *model_path(Agraph_t *modelgraph, Agnode_t *tv, Agnode_t *hv, Agedge_t *orig)
+{
+       vpath_t *p;
+       Agnode_t        *origlow, *orighigh;
+       double          old, new, tot, hx, tx;
+       port                    tp, hp;
+       int                     i;
+
+       getlowhigh(&tv,&hv);
+       origlow = orig->tail; orighigh = orig->head;
+       getlowhigh(&origlow,&orighigh);
+
+       p = pathsearch(modelgraph,origlow,orighigh);
+       if (!p) {
+               p = newpath(origlow,orighigh);
+               for (i = p->low; i <= p->high; i++) {
+                       if (i == p->low) p->v[i] = tv;
+                       else if (i == p->high) p->v[i] = hv;
+                       else p->v[i] = model_vnode(modelgraph,orig,i);
+                       if (i > p->low)
+                               p->e[i-1] = model_vedge(modelgraph,p->v[i-1],p->v[i],orig);
+               }
+               pathinsert(modelgraph,p);
+       }
+       /* merge the weights on the vedges */
+               /* SN: I think this code is only correct if the parallel edges all
+                * refer to the same original endpoint, which is not necessarily true.
+                * However, the correct value will be computed later by subclustports()
+                */
+       old = p->weight; new = ED_weight(orig); tot = old + new;
+       if (origlow == orig->tail) {tp = ED_tailport(orig); hp = ED_headport(orig);}
+       else {tp = ED_headport(orig); hp = ED_tailport(orig);}
+       tx = tp.p.x; if (tx > PORTCLAMP) tx = PORTCLAMP; if (tx < -PORTCLAMP) tx = -PORTCLAMP;
+       hx = hp.p.x; if (hx > PORTCLAMP) hx = PORTCLAMP; if (hx < -PORTCLAMP) hx = -PORTCLAMP;
+       p->tailport = (old/tot)*tx + (new/tot)*p->tailport;
+       p->headport = (old/tot)*hx + (new/tot)*p->headport;
+       p->weight = tot;
+       return p;
+}
+
+/* get model node for an endpoint (can be external to the clust).  assumes
+each node and cluster has a unique parent, and nodes have unique names.
+clust and node are from a user graph, not an internal model graph. 
+*/
+static Agnode_t *model_getrep(Agraph_t *uclust, Agnode_t *un, Agedge_t *ue)
+{
+       Agraph_t        *model = GD_model(uclust);
+       Agraph_t        *subclust;
+       vpath_t         *skel;
+       Agnode_t        *rep;
+
+       if (agcontains(uclust,un)) {    /* internal */
+               if (ND_cluster(un) == uclust) { /* primitive */
+                       rep = ND_rep(un);
+                       if (!rep) {
+                               /* i don't believe this should ever really happen because
+                               all real nodes were already processed; consider this safety code  */
+                               rep = model_realnode(model,un);
+                       }
+               }
+               else {                                                  /* in a subcluster */
+                       subclust = subclustof(un,uclust);
+                       skel = model_skel(model,subclust);
+                       rep = skel->v[ND_rank(un)];
+               }
+       }
+       else { /* external */
+               rep = model_extnode(model,un,ue);
+       }
+       return rep;
+}
+
+static vpath_t *model_skel(Agraph_t *model, Agraph_t *uclust)
+{
+       vpath_t         *skel;
+       int                             i;
+
+       if (!((skel = GD_skel(uclust)))) {
+               skel = GD_skel(uclust) = NEW(vpath_t);
+               skel->key.tail = skel->key.head = NILnode;      /* skeletons aren't indexed */
+               skel->low = GD_minrank(uclust);
+               skel->high = GD_maxrank(uclust);
+               skel->v = nodearray(skel->low,skel->high);
+               skel->e = edgearray(skel->low,skel->high);
+               for (i = skel->low; i <= skel->high; i++) {
+                       skel->v[i] = model_cnode(model,uclust,i);
+                       if (i > skel->low)
+                               skel->e[i - 1] = model_cedge(model,skel->v[i-1],skel->v[i]);
+               }
+       }
+       return skel;
+}
+
+static void rec_open_modelgraphs(Agraph_t *ug)
+{
+       Agraph_t        *universe, *model, *subg;
+       Agnode_t        *n;
+
+       char nametmp[1024];     /* fix this! */
+
+       if (is_a_cluster(ug)) {
+               universe = universegraph(ug);
+               sprintf(nametmp,"model_of_%s",ug->name);
+               model = GD_model(ug) = agsubg(universe,nametmp);
+               if (!(n = agfstnode(ug))) return;
+               GD_minrank(ug) = GD_maxrank(ug) = ND_rank(n);
+               while ((n = agnxtnode(ug,n))) {
+                       if (GD_maxrank(ug) < ND_rank(n)) GD_maxrank(ug) = ND_rank(n);
+                       if (GD_minrank(ug) > ND_rank(n)) GD_minrank(ug) = ND_rank(n);
+               }
+               GD_minrank(model) = GD_minrank(ug);
+               GD_maxrank(model) = GD_maxrank(ug);
+       }
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_open_modelgraphs(subg);
+}
+
+static void rec_skel(Agraph_t *uclust)
+{
+       Agraph_t        *model;
+
+       if (!uclust) return;
+       model = GD_model(uclust);
+       if (GD_skel(model)) return;             /* already done ???? */
+               /* skeleton is made within the model of the parent of uclust */
+       if (GD_parent(uclust))
+               (void) model_skel(GD_model(GD_parent(uclust)), uclust);
+       rec_skel(GD_parent(uclust));
+}
+
+static void make_nodes(Agraph_t *ug)
+{
+       Agnode_t        *n;
+       Agraph_t        *model;
+
+       if ((n = agfstnode(ug)) == NILnode) return;
+       rec_open_modelgraphs(ug);
+       model = GD_model(ug);
+
+       /* the following creates real and cluster skeleton nodes
+          in their original graph order.  vnodes get done later. */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               (void) model_realnode(GD_model(ND_cluster(n)),n);
+               if (ND_cluster(n) != ug) rec_skel(ND_cluster(n));
+       }
+}
+
+static int leftmost(Agraph_t *model, int r) { return 0; }
+static int rightmost(Agraph_t *model, int r) {return GD_rank(model)[r].n - 1;}
+
+/* Build a model graph for each cluster + its ext edges.
+   The model is a subgraph of a 'universal graph' for all
+        the objects of a graph and its clusters.  This makes it
+        possible to merge the cluster (models) later.  Fortunately
+        each primitive node only belongs to one cluster.  On the
+        other hand, naming all the virtual objects uniquely is so much
+        useless overhead.  (This is one thing libAgraph handles reasonably
+        and that it might make sense to port back to libgraph.)
+
+        Note that the cluster models are not themselves nested subgraphs.
+        This is because we don't want internal objects of a cluster to
+        propagate to its enclosing clusters.
+ */
+static void rec_cluster_model(Agraph_t *ug)
+{
+       graph_t         *model,*subg;
+       node_t          *n;
+       edge_t          *e;
+       node_t          *tx, *hx;
+       int                             minr,maxr,r,*ranksize;
+
+       if (is_a_cluster(ug)) {
+               /* get model graph */
+               model = GD_model(ug);
+
+               /* edges (including external ones) */
+               for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+                       for (e = agfstedge(ug->root,n); e; e = agnxtedge(ug->root,e,n)) {
+                               tx = model_getrep(ug,e->tail,e);
+                               hx = model_getrep(ug,e->head,e);
+                               if (tx == hx) continue;
+                               /* skip edges that are within some subcluster */
+                               if ((ND_type(tx)==NODETYPE_CNODE) && (ND_type(hx)==NODETYPE_CNODE) &&
+                                       (ND_cluster(tx) == ND_cluster(hx))) continue; 
+                               (void) model_path(model,tx,hx,e);
+                       }
+               }
+
+               /* initialize storage for later installation of nodes */
+
+                       /* external nodes can be outside the internal min/max rank bounds */
+               minr = GD_minrank(model);
+               maxr = GD_maxrank(model);
+               for (n = agfstnode(model); n; n = agnxtnode(model,n)) {
+                       minr = MIN(minr,ND_rank(n));
+                       maxr = MAX(maxr,ND_rank(n));
+               }
+
+               ranksize = intarray(minr,maxr);
+               for (n = agfstnode(model); n; n = agnxtnode(model,n))
+                       ranksize[ND_rank(n)]++;
+               GD_rank(model) = T_array(minr,maxr,sizeof(rank_t));
+               for (r = minr; r <= maxr; r++) /* see leftmost() and rightmost() */
+                       GD_rank(model)[r].v = N_NEW(ranksize[r]+1,Agnode_t*);   /* NIL at end */
+       }
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_model(subg);
+}
+
+static void cluster_model(Agraph_t *ug)
+{
+       Agraph_t        *g;
+       Agnode_t        *n;
+       Agedge_t        *e;
+       int                     indeg, outdeg;
+
+       rec_cluster_model(ug);
+
+       g = GD_model(ug);
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               indeg = outdeg = 0;
+               if (ND_indeg(n) == 0) {
+                       for (e = agfstin(g,n); e; e = agnxtin(g,e)) indeg++;
+                       ND_indeg(n) = indeg;
+               }
+               if (ND_outdeg(n) == 0) {
+                       for (e = agfstout(g,n); e; e = agnxtout(g,e)) outdeg++;
+                       ND_outdeg(n) = outdeg;
+               }
+               GD_maxinoutdeg(g->root) = MAX(GD_maxinoutdeg(g->root),MAX(indeg,outdeg));
+       }
+
+       /* set up mincross running parameters */
+       GD_pass(g) = 0;
+       GD_lastwin(g) = 0;
+       GD_mintry(g) = gvgetint(ug,"minpass",24);
+       GD_maxpass(g) = gvgetint(ug,"maxpass",1024);
+       GD_bestcrossings(g) = MAXINT;
+}
+
+static Agraph_t *universegraph(Agraph_t *ug)
+{
+       graph_t         *root = ug->root;
+       graph_t         *univ,*model;
+       if ((model = GD_model(root))) 
+               univ = model->root;
+       else {
+               univ = agopen("_universe\001_",AGDIGRAPHSTRICT);
+       }
+       return univ;
+}
+
+static void flat_edges(Agraph_t *clust)
+{
+#ifdef NOTDEF
+       for (n = agfstnode(clust); n; n = agnxtnode(clust)) {
+               for (e = agfstedge(root,n); e; e = agnxtedge(root,e,n)) {
+               }
+       }
+       ordered_edges();
+#endif
+}
+
+static void search_component(Agraph_t *g, Agnode_t *n, int c)
+{
+       Agedge_t        *e;
+       ND_component(n) = c;
+       for (e = agfstout(g,n); e; e = agnxtout(g,e))
+               if (ND_component(e->head) < 0)
+                       search_component(g,e->head,c);
+       for (e = agfstin(g,n); e; e = agnxtin(g,e))
+               if (ND_component(e->tail) < 0)
+                       search_component(g,e->tail,c);
+}
+
+static int ND_comp_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_component(*(Agnode_t**)arg0) - ND_component(*(Agnode_t**)arg1);
+}
+
+static component_t build_components(Agraph_t *g, boolean down)
+{
+       component_t     rv;
+       node_t  *n;
+       int             r, rootcnt, compcnt, deg;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_component(n) = -1;   /* initialize to unknown component */
+
+       compcnt = 0; rootcnt = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) { 
+               /* set priority for subsequent BFS to install nodes, and record roots */
+               if (down) deg = ND_indeg(n);
+               else deg = ND_outdeg(n);
+               ND_priority(n) = deg;
+               if (deg == 0) rootcnt++;
+               /* count and mark components */
+               if (ND_component(n) < 0) search_component(g,n,compcnt++);
+       }
+
+       rv.n = compcnt;
+       rv.r = rootcnt;
+       rv.root = N_NEW(rv.r,Agnode_t*);
+       r = 0;
+       /* install roots in root list */
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               if (ND_priority(n) == 0) rv.root[r++] = n;
+       /* sort root list so components are contiguous */
+       qsort(rv.root,rv.n,sizeof(node_t*),ND_comp_cmpf);
+       return rv;
+}
+
+static void install(Agraph_t *g, Agnode_t *n)
+{
+       int                             rank;
+       rank_t          *r;
+
+       rank = ND_rank(n);
+       r = &GD_rank(g)[rank];
+       r->v[r->n] = n;
+       ND_order(n) = r->n++;
+}
+
+/* 
+ populates rank lists of g.  there are some key details:
+ 1) the input graph ordering must be respected (in left to right initialization)
+ 2) connected components are separated and marked with indices
+ 3) series-parallel graphs (includes trees, obviously) must not have crossings
+*/
+static void build_ranks(Agraph_t *g, boolean down)
+{
+       queue                   *q;
+       component_t c;
+       int                             r;
+       Agnode_t        *n;
+       Agedge_t        *e;
+
+       c = build_components(g, down);
+
+       /* process each each component */
+       q = new_queue(agnnodes(g)+1);
+       for (r = 0; r < c.r; r++) {
+               enqueue(q,c.root[r]);
+               if ((r + 1 >= c.r)||(ND_component(c.root[r])!=ND_component(c.root[r+1]))) {
+                       while ((n = dequeue(q))) {
+                               install(g,n);
+                                       if (down) {
+                                               for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                                                       if (--ND_priority(e->head) == 0) enqueue(q,e->head);
+                                       }
+                                       else {
+                                               for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                                                       if (--ND_priority(e->tail) == 0) enqueue(q,e->head);
+                                       }
+                       }
+               }
+       }
+       free_queue(q);
+}
+
+/* this predicate indicates if mincross should be run on this cluster */
+static boolean run(Agraph_t *mg)
+{
+       if (GD_pass(mg) > GD_maxpass(mg)) return FALSE;
+       if (GD_pass(mg) - GD_lastwin(mg) > GD_mintry(mg)) return FALSE;
+       GD_pass(mg) = GD_pass(mg) + 1;
+       return TRUE;
+}
+
+static int presort_cmpf(const void *arg0, const void *arg1)
+{
+       Agnode_t        *n0, *n1;
+       Agraph_t        *c0, *c1;
+
+       n0 = *(Agnode_t**)arg0;
+       n1 = *(Agnode_t**)arg1;
+       c0 = ND_cluster(n0);
+       c1 = ND_cluster(n1);
+       if (c0 == c1) return 0;
+       assert(ND_rank(n0) == ND_rank(n1));
+       n0 = GD_skel(c0)->v[ND_rank(n0)];
+       n1 = GD_skel(c1)->v[ND_rank(n1)];
+       return ND_order(n0) - ND_order(n1);
+}
+
+static void presort(Agraph_t *ug)
+{
+       int             r;
+       int             i;
+       Agraph_t        *mg;
+
+       if (ug == ug->root) return;
+       mg = GD_model(ug);
+       for (r = GD_minrank(mg); r <= GD_maxrank(mg); r++) {
+               qsort(GD_rank(mg)[r].v,GD_rank(mg)[r].n,sizeof(Agnode_t*),presort_cmpf);
+               for (i = leftmost(mg,r); i < rightmost(mg,r); i++)
+                       ND_order(GD_rank(mg)[r].v[i]) = i;
+       }
+}
+
+/* sets ports that represent connections to subclusters */
+static void subclustports(Agraph_t *ug)
+{
+       Agraph_t        *model, *clustmodel;
+       Agnode_t        *x;
+       Agedge_t        *e;
+       vpath_t         *p;
+       Dict_t          *d;
+       double          frac;
+
+       /* walk all the paths */
+       model = GD_model(ug);
+       d = pathdict(model);
+       for (p = dtfirst(d); p; p = dtnext(d,p)) {
+               if ((ND_type(p->v[p->low])) == NODETYPE_CNODE) {
+                       x = p->v[p->low];
+                       clustmodel = GD_model(ND_cluster(x));
+                       frac = (ND_order(x) + 1) / GD_rank(clustmodel)[ND_rank(x)].n;
+                       e = p->e[p->low];
+                       ED_tailport(e).p.x = 2 * PORTCLAMP * (frac - 0.5) + p->tailport;
+               }
+               if ((ND_type(p->v[p->high])) == NODETYPE_CNODE) {
+                       x = p->v[p->high];
+                       clustmodel = GD_model(ND_cluster(x));
+                       frac = (ND_order(x) + 1) / GD_rank(clustmodel)[ND_rank(x)].n;
+                       e = p->e[p->high-1];
+                       ED_headport(e).p.x = 2 * PORTCLAMP * (frac - 0.5) + p->headport;
+               }
+       }
+}
+
+static void mincross_clust(Agraph_t *ug)
+{
+       Agraph_t        *g;
+       g = GD_model(ug);
+       if (run(g)) {
+               presort(ug);            /* move the external nodes */
+               subclustports(ug);
+               do {
+                       mincross_sweep(g,GD_pass(g)%2,GD_pass(g)%4<2);
+               } while (run(g));
+               transpose_sweep(g,TRUE);
+               restorebest(g);
+       }
+}
+
+static void globalopt(Agraph_t *root)
+{
+       Agraph_t        *g;
+       rank_t          *glob;
+
+       g = GD_model(root);
+       glob = globalize(root,g);
+       GD_rank(g) = glob;
+       fprintf(stderr,"%s: %d crossings\n",root->name,crossings(g));
+}
+
+/* this assumes one level per node - no mega-nodes */
+static void apply_model(Agraph_t *ug)
+{
+       Agnode_t *un;
+       for (un = agfstnode(ug); un; un = agnxtnode(ug,un)) 
+                       ND_order(un) = ND_order(ND_rep(un));
+}
+
+/* this is a first cut at a top-level planner.  it's lame. */
+static void rec_cluster_run(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) mincross_clust(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_run(subg);
+       if (is_a_cluster(ug)) mincross_clust(ug);
+}
+
+/* this is the top level mincross entry point */
+void dot_mincross(Agraph_t *user)
+{
+       rec_cluster_init(user);
+       rec_cluster_run(user);
+       globalopt(user);
+       apply_model(user);
+}
+
+static void invalidate(Agraph_t *g, int rank)
+{
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].crossing_cache.valid = FALSE;
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].candidate = TRUE;
+       if (rank < GD_maxrank(g)) GD_rank(g)[rank+1].candidate = TRUE;
+}
+
+/* swaps two nodes in the same level */
+static void exchange(Agraph_t *g, Agnode_t *u, Agnode_t *v)
+{
+       rank_t  *r;
+       int                     ui,vi,rank;
+
+       assert(ND_rank(u) == ND_rank(v));
+       rank = ND_rank(u);
+       r = &GD_rank(g)[rank];
+       ui = ND_order(u);
+       vi = ND_order(v);
+       ND_order(v) = ui;
+       ND_order(u) = vi;
+       r->v[ND_order(u)] = u;
+       r->v[ND_order(v)] = v;
+       r->crossing_cache.valid = FALSE;
+       r->changed = TRUE;
+       r->candidate = TRUE;    /* old dot had this.  i have qualms. sn */
+       invalidate(g,rank);
+}
+
+int transpose_onerank(Agraph_t* g, int r, boolean reverse)
+{
+       int     i,c0,c1,rv;
+       node_t  *v,*w;
+
+       rv = 0;
+       GD_rank(g)[r].candidate = FALSE;
+       for (i = leftmost(g,r); i < rightmost(g,r); i++) {
+               v = GD_rank(g)[r].v[i];
+               w = GD_rank(g)[r].v[i+1];
+               assert (ND_order(v) < ND_order(w));
+               if (left2right(g,v,w)) continue;
+               c0 = c1 = 0;
+               if (r > GD_minrank(g)) {
+                       c0 += in_cross(v,w);
+                       c1 += in_cross(w,v);
+               }
+               if (r < GD_maxrank(g)) {
+                       c0 += out_cross(v,w);
+                       c1 += out_cross(w,v);
+               }
+               if ((c1 < c0) || ((c0 > 0) && reverse && (c1 == c0))) {
+                       exchange(g,v,w);
+                       rv += (c0 - c1);
+               }
+       }
+       return rv;
+}
+
+static void transpose_sweep(Agraph_t* g, int reverse)
+{
+    int     r,delta;
+
+       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+               GD_rank(g)[r].candidate = TRUE;
+    do {
+                       delta = 0;
+                       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+                               if (GD_rank(g)[r].candidate) delta += transpose_onerank(g,r,reverse);
+    }
+               while (delta >= 1);
+               /* while (delta > crossings(g)*(1.0 - Convergence));*/
+}
+
+static void mincross_sweep(Agraph_t* g, int dir, boolean reverse)
+{
+    int     r,other,low,high,first,last;
+    int     hasfixed;
+
+               low = GD_minrank(g);
+               high = GD_maxrank(g);
+               if (dir == 0) return;
+               if (dir > 0)  { first = low + 1; last = high; dir = 1;}         /* down */
+               else                              { first = high - 1; last = low; dir = -1;}    /* up */
+
+    for (r = first; r != last + dir; r += dir) {
+        other = r - dir;
+        hasfixed = medians(g,r,other);
+        reorder(g,r,reverse,hasfixed);
+    }
+    transpose_sweep(g,NOT(reverse));
+       savebest(g);
+}
+
+
+static int left2right(Agraph_t *g, node_t *v, node_t *w)
+{
+    int         rv;
+
+#ifdef NOTDEF
+    adjmatrix_t *M;
+    M = GD_rank(g)[ND_rank(v)].flat;
+    if (M == NULL) rv = FALSE;
+    else {
+        if (GD_flip(g)) {node_t *t = v; v = w; w = t;}
+        rv = ELT(M,flatindex(v),flatindex(w));
+    }
+#else
+               rv = FALSE;
+#endif
+    return rv;
+}
+
+static void build_flat_graphs(Agraph_t *g)
+{
+}
+
+static int out_cross(node_t *v, node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int  inv, cross = 0,t;
+
+  for (e2 = agfstout(w->graph,w); e2; e2 = agnxtout(w->graph,e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(e2->head);
+               for (e1 = agfstout(v->graph,v); e1; e1 = agnxtout(v->graph,e1)) {
+                       t = ND_order(e1->head) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_headport(e1).p.x > ED_headport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+                                                                                
+static int in_cross(node_t *v,node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int inv, cross = 0, t;
+                                                                                
+  for (e2 = agfstin(w->graph,w); e2; e2 = agnxtin(w->graph,e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(e2->tail);
+               for (e1 = agfstin(v->graph,v); e1; e1 = agnxtin(v->graph,e1)) {
+                       t = ND_order(e1->tail) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_tailport(e1).p.x > ED_tailport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+
+static int int_cmpf(const void *arg0, const void *arg1)
+{
+       return *(int*)arg0 - *(int*)arg1;
+}
+
+
+/* 8 is the number of bits in port.order, an unsigned char */
+#define VAL(node,port) (((node)->u.order << 8)  + (port).order)
+
+/*
+ * defines ND_sortweight of each node in r0 w.r.t. r1
+ * returns...
+ */
+static boolean medians(Agraph_t *g, int r0, int r1)
+{
+       static int *list;
+       static int list_extent;
+       int     i,j,lm,rm,lspan,rspan;
+       node_t  *n,**v;
+       edge_t  *e;
+       boolean hasfixed = FALSE;
+
+       if (list_extent < GD_maxinoutdeg(g->root)) {
+               list_extent = GD_maxinoutdeg(g->root);
+               if (!list) list = realloc(list,sizeof(list[0])*list_extent);
+               else list = realloc(list,sizeof(list[0])*list_extent);
+       }
+       v = GD_rank(g)[r0].v;
+       for (i = leftmost(g,r0); i <= rightmost(g,r0); i++) {
+               n = v[i]; j = 0;
+               if (r1 > r0) for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(e->head,ED_headport(e));}
+               else for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(e->tail,ED_tailport(e));}
+               switch(j) {
+                       case 0:
+                               ND_sortweight(n) = -1;          /* no neighbor - median undefined */
+                               break;
+                       case 1:
+                               ND_sortweight(n) = list[0];
+                               break;
+                       case 2:
+                               ND_sortweight(n) = (list[0] + list[1])/2;
+                               break;
+                       default:
+                               qsort(list,j,sizeof(int),int_cmpf);
+                               if (j % 2) ND_sortweight(n) = list[j/2];
+                               else {
+                                       /* weighted median */
+                                       rm = j/2;
+                                       lm = rm - 1;
+                                       rspan = list[j-1] - list[rm];
+                                       lspan = list[lm] - list[0];
+                                       if (lspan == rspan)
+                                               ND_sortweight(n) = (list[lm] + list[rm])/2;
+                                       else {
+                                               int w = list[lm]*rspan + list[rm]*lspan;
+                                               ND_sortweight(n) = w / (lspan + rspan);
+                                       }
+                               }
+               }
+       }
+#ifdef NOTDEF
+       /* this code was in the old mincross */
+       for (i = 0; i < GD_rank(g)[r0].n; i++) {
+               n = v[i];
+               if ((ND_out(n).size == 0) && (ND_in(n).size == 0))
+                       hasfixed |= flat_sortweight(n);
+       }
+#endif
+       return hasfixed;
+}
+
+static void reorder(graph_t *g, int r, boolean reverse, boolean hasfixed)
+{
+       boolean changed, muststay;
+       node_t  **vlist, **lp, **rp, **ep;
+       int                     i;
+
+       changed = FALSE;
+       vlist = GD_rank(g)[r].v;
+       ep = &vlist[rightmost(g,r)];
+       
+       for (i = leftmost(g,r); i <= rightmost(g,r); i++) {
+               lp = &vlist[leftmost(g,r)];
+               /* find leftmost node that can be compared */
+               while ((lp < ep) && (ND_sortweight(*lp) < 0)) lp++;
+               if (lp >= ep) break;
+               /* find the node that can be compared */
+               muststay = FALSE;
+               for (rp = lp + 1; rp < ep; rp++) {
+                       if (left2right(g,*lp,*rp)) { muststay = TRUE; break; }
+                       if (ND_sortweight(*rp) >= 0) break;     /* weight defined; it's comparable */
+               }
+               if (rp >= ep) break;
+               if (muststay == FALSE) {
+                       register int    p1 = ND_sortweight(*lp);
+                       register int    p2 = ND_sortweight(*rp);
+                       if ((p1 > p2) || ((p1 == p2) && (reverse))) {
+                               exchange(g,*lp,*rp);
+                               changed = TRUE;
+                       }
+               }
+               lp = rp;
+               if ((hasfixed == FALSE) && (reverse == FALSE)) ep--;
+       }
+                                                                                
+       if (changed) {
+               GD_rank(g)[r].changed = TRUE;
+               GD_rank(g)[r].crossing_cache.valid = FALSE;
+               if (r > 0) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+               if (r + 1 > GD_rank(g)[r+1].n) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+       }
+}
+
+static void savebest(graph_t *g)
+{
+       int             nc;
+       Agnode_t        *n;
+
+       nc = crossings(g);
+       if (nc < GD_bestcrossings(g)) {
+               for (n = agfstnode(g); n; n = agnxtnode(g,n))
+                       ND_saveorder(n) = ND_order(n);
+               GD_bestcrossings(g) = nc;
+               GD_lastwin(g) = GD_pass(g);
+       }
+}
+
+static int ND_order_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_order(*(Agnode_t**)arg0) - ND_order(*(Agnode_t**)arg1);
+}
+
+static void restorebest(graph_t *g)
+{
+       Agnode_t        *n;
+       int                     i;
+
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++) 
+               GD_rank(g)[i].changed = FALSE;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))  {
+               if (ND_order(n) != ND_saveorder(n)) {
+                       invalidate(g,ND_rank(n));
+                       GD_rank(g)[i].changed = TRUE;
+                       ND_order(n) = ND_saveorder(n);
+               }
+       }
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++)  {
+               if (GD_rank(g)[i].changed)
+                       qsort(GD_rank(g)[i].v,GD_rank(g)[i].n,sizeof(Agnode_t*),ND_order_cmpf);
+       }
+}
+
+static int crossings(graph_t *g)
+{
+               int             i, rv;
+
+               rv = 0;
+               for (i = GD_minrank(g); i < GD_maxrank(g); i++) {
+                       rv += crossings_below(g,i);
+               }
+               return rv;
+}
+
+/* returns level of (model) node's cluster */
+static int lev(Agnode_t *n)
+{
+       if (!n) return -1;
+       return (GD_level(ND_cluster(n)));
+}
+
+/* 
+ * allocates a vpath_t per original user edge.  later the path
+ * contents will be set to define vnode chains of intercluster edges.
+ */
+static void interclusterpaths(Agraph_t *ug, Agraph_t *topmodel)
+{
+       Agnode_t        *n, *n0;
+       Agedge_t        *e, *ue;
+       vpath_t         *p;
+
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(ug,n); e; e = agnxtout(ug,e)) {
+                       if (ND_cluster(e->tail) != ND_cluster(e->head)) {
+                               if (!(p = pathsearch(topmodel->root,e->tail,e->head))) {
+                                       p = newpath(e->tail, e->head);
+                                       pathinsert(topmodel->root,p);
+                               }
+                       }
+               }
+       }
+
+       /* scan all vnodes of model graph and make decisions */
+       for (n = agfstnode(topmodel); n; n = agnxtnode(topmodel,n)) {
+               if (ND_type(n) == NODETYPE_VNODE) {
+                       ue = ND_erep(n);
+                       p = pathsearch(topmodel->root,ue->tail,ue->head);
+                       if (p) {
+                               if (!(n0 = p->v[ND_rank(n)]) || (lev(n0) < lev(n)))
+                                       p->v[ND_rank(n)] = n;
+                       }
+               }
+       }
+       /* need to remove the other edges of these vnodes!! */
+}
+
+/* build the global (flat) graph of the universe.  this is 'flat'
+in the sense that there is one data structure for the entire graph
+(not 'flat' in the sense of flat edges within the same level.)
+*/
+static rank_t *globalize(Agraph_t *user, Agraph_t *topmodel)
+{
+       rank_t  *globrank;
+       int             minr,maxr,r;
+
+       /* setup bookkeeping */
+       interclusterpaths(user, topmodel);
+
+       /* allocate global ranks */
+       minr = GD_minrank(topmodel);
+       maxr = GD_maxrank(topmodel);
+       globrank = T_array(minr,maxr,sizeof(rank_t));
+       countup(user,globrank);
+       for (r = minr; r <= maxr; r++) {
+               globrank[r].v = N_NEW(globrank[r].n+1,Agnode_t*);       /* NIL at end */
+               globrank[r].n = 0;      /* reset it */
+       }
+
+       /* installation */
+       for (r = minr; r <= maxr; r++)
+               installglob(user,topmodel,globrank,r);
+
+       removejunk(user, topmodel);
+       reconnect(user, topmodel);
+
+       /* optimization */
+       return globrank;
+}
+
+
+static void countup(Agraph_t *g, rank_t *globr)
+{
+       Agnode_t        *n;
+       Agedge_t        *e;
+       int                             r0, r1, low, high, i;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+                       globr[ND_rank(n)].n += 1;
+                       for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
+                               r0 = ND_rank(e->tail);
+                               r1 = ND_rank(e->head);
+                               low = MIN(r0,r1);
+                               high = MAX(r0,r1);
+                               for (i = low + 1; i < high; i++)
+                                       globr[i].n += 1;
+                       }
+       }
+}
+
+/* install nodes from rank r of (g or its clusters) into globr. */
+static void installglob(Agraph_t *ug, Agraph_t *fromgraph, rank_t *globr, int r)
+{
+       rank_t          *myrank;
+       int                             i;
+       Agnode_t        *v;
+       Agedge_t        *uedge;
+       vpath_t         *path;
+
+       if (is_a_cluster(fromgraph)) {
+               myrank = &GD_rank(fromgraph)[r];
+               i = 0;
+               while ((v = myrank->v[i++])) {
+                       switch (ND_type(v)) {
+                               case NODETYPE_REAL:
+                                               /* install primitive nodes */
+                                       globr[r].v[globr[r].n++] = v;
+                                       ND_isglobal(v) = TRUE;
+                                       break;
+                               case NODETYPE_VNODE:
+                                       /* install vnode for non-intercluster edges, and for intercluster
+                                                edges if this vnode was chosen as the path representative */
+                                       uedge = ND_erep(v);
+                                       path = pathsearch(fromgraph->root,uedge->tail,uedge->head);
+                                       if (!path || (path->v[r] == v)) {
+                                               globr[r].v[globr[r].n++] = v;
+                                               ND_isglobal(v) = TRUE;
+                                       }
+                                       break;
+                               case NODETYPE_CNODE:
+                                       /* install clusters recursively */
+                                       installglob(ug,ND_cluster(v),globr,r);
+                                       break;
+                               case NODETYPE_XNODE:
+                                       /* we're ignoring these */
+                                       break;
+                       }
+               }
+       }
+}
+
+/*
+       after making the global graph, delete all non-global objects.
+ */
+static void removejunk(Agraph_t *ug, Agraph_t *topmodel)
+{
+       Agnode_t        *v,*vv;
+
+       for (v = agfstnode(topmodel); v; v = vv) {
+               vv = agnxtnode(topmodel,v);
+               if (!ND_isglobal(v)) agdelete(topmodel,v);
+       }
+}
+
+/* 
+ fix up the user edge paths in the global graph.
+*/
+static void reconnect(Agraph_t *ug, Agraph_t *topmodel)
+{
+       Agnode_t        *n, *n0, *n1;
+       Agedge_t        *e, *e0;
+       vpath_t         *p;
+       int                             i;
+
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(ug,n); e; e = agnxtout(ug,e)) {
+                       if (ND_cluster(e->tail) != ND_cluster(e->head)) {
+                               p = pathsearch(topmodel->root,e->tail,e->head);
+                               n0 = p->v[p->low];
+                               for (i = p->low + 1; i <= p->high; i++) {
+                                       n1 = p->v[i];
+                                       if (!(e0 = p->e[i])) e0 = agedge(topmodel,n0,n1);
+                                       ED_weight(e0) += ED_weight(e);
+                                       ED_xpenalty(e0) += ED_xpenalty(e);
+                                       n0 = n1;
+                               }
+                       }
+               }
+       }
+}
+
+#ifdef NOTDEF
+/* (no parallel edges, all forward edges, flat cycles broken) */
+void build_main_graph(Agraph_t *g)
+{
+       Agraph_t        *gprime,*gprimeflat;
+       Agnode_t        *v,*vprime;
+       Agedge_t        *eprev;
+
+       gprime = agopen("model",AGDIGRAPHSTRICT);
+       for (v = agfstnode(g); v; v = agnxtnode(g,v)) {
+                       vprime = agnode(gprime,v->name);
+                       for (e = agfstout(v); e; e = agxntout(g,e)) {
+                               tail = e->tail;
+                               head = e->head;
+                               if (ND_rank(tail) > ND_rank(head)) {Agnode_t *t = head; head = tail; tail = t;}
+                               if (!(eprev = agfindedge(gprime,tail,head))) eprev = agedge(gprime,tail,head);
+                               merge(eprev,e);
+                               associate(e,eprev);
+                       }
+       }
+       break_cycles(gprime);
+}
+#endif
+
+/* I. initialization */
+static void cluster_init(Agraph_t *ug)
+
+{
+       Agraph_t        *mg;
+
+       make_nodes(ug);
+       cluster_model(ug);
+       mg = GD_model(ug);
+       flat_edges(mg);
+       build_ranks(mg,TRUE);
+}
+
+static void rec_cluster_init(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) cluster_init(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_init(subg);
+}
diff --git a/lib/dotgen2/t.c b/lib/dotgen2/t.c
new file mode 100644 (file)
index 0000000..2ae497c
--- /dev/null
@@ -0,0 +1,1128 @@
+#include "newdot.h"
+
+/* given:
+ *  each node and cluster has a unique containing cluster.
+ *     ND_level is the integer level of each node.
+ *  ND_cluster is the lowest containing cluster of each node.
+ *  ED_xpenalty is the crossing weight for an edge.
+ * find:
+ *  GD_rank - the levels of the graph
+ *  ND_order - the global order of each node
+ */
+
+typedef struct component_s {
+       int             n;
+       node_t  **root;
+       int r;
+} component_t;
+
+static Agraph_t *subclustof(Agnode_t *n, Agraph_t *clust);
+static void *T_array(int low, int high, int size);
+static Agnode_t **nodearray(int low, int high);
+static int *intarray(int low, int high);
+static Agraph_t *cluster_model(Agraph_t *clust);
+static void flat_edges(Agraph_t *clust);
+static void search_component(Agraph_t *g, Agnode_t *n, int c);
+static int ND_comp_cmpf(const void *arg0, const void *arg1);
+static component_t build_components(Agraph_t *g, boolean down);
+static void install(Agraph_t *g, Agnode_t *n);
+static void build_ranks(Agraph_t *ug, boolean down);
+static void cluster_init(Agraph_t *userclust);
+static void rec_cluster_init(Agraph_t *userclust);
+static boolean run(Agraph_t *g);
+static void mincross_clust(Agraph_t *clust);
+static void globalopt(Agraph_t *ug);
+static void rec_cluster_run(Agraph_t *ug);
+static void exchange(Agraph_t *g, Agnode_t *u, Agnode_t *v);
+static void transpose_sweep(graph_t* g, int reverse);
+static void mincross_sweep(graph_t* g, int dir, boolean reverse);
+static int left2right(graph_t *g, node_t *v, node_t *w);
+static void build_flat_graphs(graph_t *g);
+static boolean medians(graph_t *g, int r0, int r1);
+static void reorder(graph_t *g, int r, boolean reverse, boolean hasfixed);
+static void savebest(graph_t *g);
+static void restorebest(graph_t *g);
+static int crossings(graph_t *g);
+static int in_cross(node_t *v,node_t *w);
+static int out_cross(node_t *v,node_t *w);
+static rank_t *globalize(Agraph_t *user, Agraph_t *model);
+static void installglob(Agraph_t *user, Agraph_t *fromgraph, rank_t *globr, int r);
+static void countup(Agraph_t *g, rank_t *globr);
+
+static Agnode_t *model_getrep(Agraph_t *ug, Agnode_t *n, Agedge_t *e);
+static Agraph_t *universegraph(Agraph_t *ug);
+static vpath_t *model_skel(Agraph_t *uclust, Agraph_t *model);
+static void clusterchains(Agraph_t *model, Agraph_t *ug);
+static void removejunk(Agraph_t *ug, Agraph_t *topmodel);
+static void reconnect(Agraph_t *ug, Agraph_t *topmodel);
+
+/* from level.c - eventually clean this up. */
+static int is_a_cluster(graph_t *g)
+{
+       return ((g == g->root) || (!strncasecmp(g->name,"cluster",7)));
+}
+
+/* find the cluster of n that is an immediate child of g */
+static Agraph_t *subclustof(Agnode_t *n, Agraph_t *g)
+{
+       Agraph_t                *rv;
+       for (rv = ND_cluster(n); rv && (GD_parent(rv) != g); rv = GD_parent(rv));
+       return rv;
+}
+
+static void *T_array(int low, int high, int size)
+{
+       char    *rv;
+
+       rv = calloc((high - low + 1),size);
+       rv = rv - (low * size);
+       return rv;
+}
+
+static Agnode_t **nodearray(int low, int high)
+{ return (Agnode_t**) T_array(low,high,sizeof(Agnode_t*)); }
+
+static Agedge_t **edgearray(int low, int high)
+{ return (Agedge_t**) T_array(low,high,sizeof(Agedge_t*)); }
+
+static int *intarray(int low, int high)
+{ return (int*) T_array(low,high,sizeof(int)); }
+
+static Agnode_t *model_realnode(Agraph_t *modelgraph, Agnode_t *orig)
+{
+       Agnode_t        *rep;
+       /* this is OK because realnode can only be in one model graph/subgraph */
+       rep = agnode(modelgraph,orig->name);
+       ND_rank(rep) = ND_rank(orig);
+       ND_cluster(rep) = modelgraph;
+       ND_vrep(orig) = rep;
+       ND_vrep(rep) = orig;
+       ND_type(rep) = NODETYPE_REAL;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agnode_t *model_extnode(Agraph_t *modelgraph, Agnode_t *orig, Agedge_t *e)
+{
+       char name[1024];
+       Agnode_t        *rep;
+
+       sprintf(name,"ext_%s_%s",modelgraph->name,orig->name);
+       /* can't be anonymous - needs to be repeatable */
+       rep = agnode(modelgraph,name);
+       ND_rank(rep) = ND_rank(orig);
+       ND_cluster(rep) = modelgraph;
+       ND_erep(rep) = e;
+       ND_type(rep) = NODETYPE_XNODE;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agnode_t *model_vnode(Agraph_t *modelgraph, Agedge_t *longedge, int level)
+{
+       Agnode_t        *rep;
+       rep = agnode(modelgraph,0);
+       ND_rank(rep) = level;
+       ND_cluster(rep) = modelgraph;
+       ND_erep(rep) = longedge;
+       ND_type(rep) = NODETYPE_VNODE;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agnode_t *model_cnode(Agraph_t *modelgraph, Agraph_t *clust, int level)
+{
+       Agnode_t        *rep;
+       rep = agnode(modelgraph,0);
+       ND_rank(rep) = level;
+       ND_cluster(rep) = clust;
+       ND_grep(rep) = clust;
+       ND_type(rep) = NODETYPE_CNODE;
+       /* other initializations may need to go here */
+       return rep;
+}
+
+static Agedge_t *model_vedge(Agraph_t *modelgraph, Agnode_t *tx, Agnode_t *hx, Agedge_t *orig)
+{
+       Agedge_t        *e;
+
+       e = agedge(modelgraph,tx,hx);
+       ED_xpenalty(e) = VEDGE_PENALTY * ED_xpenalty(orig);
+       ED_weight(e) = ED_weight(orig);
+       return e;
+}
+
+static Agedge_t *model_cedge(Agraph_t *modelgraph, Agnode_t *tx, Agnode_t *hx)
+{
+       Agedge_t        *e;
+
+       e = agedge(modelgraph,tx,hx);
+       ED_xpenalty(e) = CEDGE_PENALTY;
+       ED_weight(e) = 1;
+       return e;
+}
+
+static void getlowhigh(Agnode_t **n0, Agnode_t **n1)
+{
+       Agnode_t        *temp;
+       if (ND_rank(*n0) > ND_rank(*n1))
+               {temp = *n0; *n0 = *n1; *n1 = temp;}
+}
+
+vpath_t *newpath(Agnode_t *u, Agnode_t *v)
+{
+       Agnode_t        *origlow = u, *orighigh = v;
+       vpath_t         *p;
+
+       getlowhigh(&origlow,&orighigh);
+       p = NEW(vpath_t);
+       p->key.tail = origlow;
+       p->key.head = orighigh;
+       p->low = ND_rank(origlow);
+       p->high = ND_rank(orighigh);
+       p->v = nodearray(p->low, p->high);
+       p->e = edgearray(p->low, p->high);
+       return p;
+}
+
+static int pathcmp(Dt_t *d, void *arg0, void *arg1, Dtdisc_t *disc)
+{
+       int             rv;
+       if ((rv = ((vpath_t*)arg0)->key.tail - ((vpath_t*)arg1)->key.tail) == 0)
+               rv = ((vpath_t*)arg0)->key.head - ((vpath_t*)arg1)->key.head;
+       return rv;
+}
+
+static Dtdisc_t Vpathdisc = {
+       0,                      /* pass whole object as key */
+       0,                      /* key size and type */
+       -1,                     /* link offset */
+       (Dtmake_f)0,
+       (Dtfree_f)0,
+       (Dtcompar_f) pathcmp,
+       (Dthash_f)0,
+       (Dtmemory_f)0,
+       (Dtevent_f)0
+};
+
+static Dict_t *pathdict(Agraph_t *g)
+{
+       Dict_t  *dict;
+       dict = GD_pathdict(g);
+       if (!dict) dict = GD_pathdict(g) = dtopen(&Vpathdisc,Dttree);
+       return dict;
+}
+
+static vpath_t *pathsearch(Agraph_t *g, Agnode_t *u, Agnode_t *v)
+{
+       vpath_t *p,path;
+       Dict_t  *dict;
+       Agnode_t        *origlow = u, *orighigh = v;
+
+       getlowhigh(&origlow, &orighigh);
+       dict = pathdict(g);
+       path.key.tail = origlow;
+       path.key.head = orighigh;
+       p = dtsearch(dict,&path);
+       return p;
+}
+
+static void pathinsert(Agraph_t *g, vpath_t *p)
+{
+       Dict_t  *dict;
+       dict = pathdict(g);
+       p = dtinsert(dict,p);
+}
+
+vpath_t *model_path(Agraph_t *modelgraph, Agnode_t *tx, Agnode_t *hx, Agedge_t *orig)
+{
+       vpath_t *p;
+       Agnode_t        *origlow, *orighigh;
+       int                     i;
+
+       getlowhigh(&tx,&hx);
+       origlow = orig->tail; orighigh = orig->head;
+       getlowhigh(&origlow,&orighigh);
+
+       p = pathsearch(modelgraph,origlow,orighigh);
+       if (!p) {
+               p = newpath(origlow,orighigh);
+               for (i = p->low; i <= p->high; i++) {
+                       if (i == p->low) p->v[i] = tx;
+                       else if (i == p->high) p->v[i] = hx;
+                       else p->v[i] = model_vnode(modelgraph,orig,i);
+                       if (i > p->low)
+                               p->e[i] = model_vedge(modelgraph,p->v[i-1],p->v[i],orig);
+               }
+               pathinsert(modelgraph,p);
+       }
+       return p;
+}
+
+/* get model node for an endpoint (can be external to the clust).  assumes
+each node and cluster has a unique parent, and nodes have unique names.
+clust and node are from a user graph, not an internal model graph. 
+*/
+static Agnode_t *model_getrep(Agraph_t *uclust, Agnode_t *un, Agedge_t *ue)
+{
+       Agraph_t        *model = GD_model(uclust);
+       Agraph_t        *subclust;
+       vpath_t         *skel;
+       Agnode_t        *rep;
+
+       if (agcontains(uclust,un)) {    /* internal */
+               if (ND_cluster(un) == uclust) { /* primitive */
+                       rep = ND_rep(un);
+                       if (!rep) {
+                               /* i don't believe this should ever really happen because
+                               all real nodes were already processed; consider this safety code  */
+                               rep = model_realnode(model,un);
+                       }
+               }
+               else {                                                  /* in a subcluster */
+                       subclust = subclustof(un,uclust);
+                       skel = model_skel(model,subclust);
+                       rep = skel->v[ND_rank(un)];
+               }
+       }
+       else { /* external */
+               rep = model_extnode(model,un,ue);
+       }
+       return rep;
+}
+
+static vpath_t *model_skel(Agraph_t *model, Agraph_t *uclust)
+{
+       vpath_t         *skel;
+       int                             i;
+
+       if (!((skel = GD_skel(uclust)))) {
+               skel = GD_skel(uclust) = NEW(vpath_t);
+               skel->key.tail = skel->key.head = NILnode;      /* skeletons aren't indexed */
+               skel->low = GD_minrank(uclust);
+               skel->high = GD_maxrank(uclust);
+               skel->v = nodearray(skel->low,skel->high);
+               skel->e = edgearray(skel->low,skel->high);
+               for (i = skel->low; i <= skel->high; i++) {
+                       skel->v[i] = model_cnode(model,uclust,i);
+                       if (i > skel->low)
+                               skel->e[i - 1] = model_cedge(model,skel->v[i-1],skel->v[i]);
+               }
+       }
+       return skel;
+}
+
+static void scangraph(Agraph_t *g)
+{
+       Agnode_t        *n;
+       Agedge_t        *e;
+       short                   indeg, outdeg;
+
+       if ((n = agfstnode(g)) == NILnode) return;
+
+       model = universegraph(g);
+       GD_minrank(g) = GD_maxrank(g) = ND_rank(n);
+       GD_maxinoutdeg(g) = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+               if (GD_maxrank(g) < ND_rank(n)) GD_maxrank(g) = ND_rank(n);
+               if (GD_minrank(g) > ND_rank(n)) GD_minrank(g) = ND_rank(n);
+               if (ND_indeg(n) == 0) {
+                       indeg = 0; for (e = agfstin(g,n); e; e = agnxtin(g,e)) indeg++;
+                       ND_indeg(n) = indeg;
+               }
+               if (ND_outdeg(n) == 0) {
+                       outdeg = 0; for (e = agfstout(g,n); e; e = agnxtout(g,e)) outdeg++;
+                       ND_outdeg(n) = outdeg;
+               }
+               GD_maxinoutdeg(g) = MAX(GD_maxinoutdeg(g),MAX(indeg,outdeg));
+       }
+}
+
+/* Build a model graph for each cluster + its ext edges.
+   The model is a subgraph of a 'universal graph' for all
+        the objects of a graph and its clusters.  This makes it
+        possible to merge the cluster (models) later.  Fortunately
+        each primitive node only belongs to one cluster.  On the
+        other hand, naming all the virtual objects uniquely is so much
+        useless overhead.  (This is one thing libAgraph handles reasonably
+        and that it might make sense to port back to libgraph.)
+
+        Note that the cluster models are not thesmselves nested subgraphs.
+        This is because we don't want internal objects of a cluster to
+        propagate to its enclosing clusters.
+        */
+static Agraph_t *cluster_model(Agraph_t *ug)
+{
+       graph_t         *model,*universe;
+       node_t          *n;
+       edge_t          *e;
+       node_t          *tx, *hx;
+       int                             minr,maxr,r,*ranksize;
+
+       /* get model graph */
+       if (GD_model(ug)) abort(); /* double initialization? not yet re-entrant. */
+       universe = universegraph(ug);
+       model = GD_model(ug) = agsubg(universe,ug->name);
+
+       /* create nodes explicitly.  this copies the search order of original nodes
+          and ensures singletons (including those in subclusters) are created */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               if (ND_cluster(n) == ug) (void) model_realnode(model,n);
+               else clusterchains(model,subclustof(n,ug));
+       }
+
+       /* edges (including external ones) */
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstedge(ug->root,n); e; e = agnxtedge(ug->root,e,n)) {
+                       tx = model_getrep(ug,e->tail,e);
+                       hx = model_getrep(ug,e->head,e);
+                       if (tx == hx) continue;
+                       /* skip edges that are within some subcluster */
+                       if ((ND_type(tx) == NODETYPE_CNODE) && (ND_type(hx) == NODETYPE_CNODE) &&
+                               (ND_cluster(tx) == ND_cluster(hx))) continue; 
+                       (void) model_path(model,tx,hx,e);
+               }
+       }
+
+       /* initialize storage for ranks in the model; nodes are installed later */
+       GD_minrank(model) = GD_minrank(ug);
+       GD_maxrank(model) = GD_maxrank(ug);
+       minr = GD_minrank(model);
+       maxr = GD_maxrank(model);
+       ranksize = intarray(minr,maxr);
+       for (n = agfstnode(model); n; n = agnxtnode(model,n))
+               ranksize[ND_rank(n)]++;
+       GD_rank(model) = T_array(minr,maxr,sizeof(rank_t));
+       for (r = minr; r <= maxr; r++)
+               GD_rank(model)[r].v = N_NEW(ranksize[r]+1,Agnode_t*);   /* NIL at end */
+
+       /* set up mincross running parameters */
+       GD_pass(model) = 0;
+       GD_lastwin(model) = 0;
+       GD_mintry(model) = gvgetint(ug,"minpass",24);
+       GD_maxpass(model) = gvgetint(ug,"maxpass",1024);
+       GD_bestcrossings(model) = MAXINT;
+       return model;
+}
+
+static Agraph_t *universegraph(Agraph_t *ug)
+{
+       graph_t         *root = ug->root;
+       graph_t         *univ,*model;
+       if ((model = GD_model(root))) 
+               univ = model->root;
+       else {
+               univ = agopen("_universe\001_",AGDIGRAPHSTRICT);
+       }
+       return univ;
+}
+
+static void clusterchains(Agraph_t *model, Agraph_t *ug)
+{
+       if (GD_clusterchainsdone(ug)) return;
+       if (is_a_cluster(ug))
+               (void) model_skel(model,ug);
+       GD_clusterchainsdone(ug) = TRUE;
+}
+
+static void flat_edges(Agraph_t *clust)
+{
+#ifdef NOTDEF
+       for (n = agfstnode(clust); n; n = agnxtnode(clust)) {
+               for (e = agfstedge(root,n); e; e = agnxtedge(root,e,n)) {
+               }
+       }
+       ordered_edges();
+#endif
+}
+
+static void search_component(Agraph_t *g, Agnode_t *n, int c)
+{
+       Agedge_t        *e;
+       ND_component(n) = c;
+       for (e = agfstout(g,n); e; e = agnxtout(g,e))
+               if (ND_component(e->head) < 0)
+                       search_component(g,e->head,c);
+       for (e = agfstin(g,n); e; e = agnxtin(g,e))
+               if (ND_component(e->tail) < 0)
+                       search_component(g,e->tail,c);
+}
+
+static int ND_comp_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_component(*(Agnode_t**)arg0) - ND_component(*(Agnode_t**)arg1);
+}
+
+static component_t build_components(Agraph_t *g, boolean down)
+{
+       component_t     rv;
+       node_t  *n;
+       int             r, rootcnt, compcnt, deg;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               ND_component(n) = -1;   /* initialize to unknown component */
+
+       compcnt = 0; rootcnt = 0;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) { 
+               /* set priority for subsequent BFS to install nodes, and record roots */
+               if (down) deg = ND_indeg(n);
+               else deg = ND_outdeg(n);
+               ND_priority(n) = deg;
+               if (deg == 0) rootcnt++;
+               /* count and mark components */
+               if (ND_component(n) < 0) search_component(g,n,compcnt++);
+       }
+
+       rv.n = compcnt;
+       rv.r = rootcnt;
+       rv.root = N_NEW(rv.r,Agnode_t*);
+       r = 0;
+       /* install roots in root list */
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))
+               if (ND_priority(n) == 0) rv.root[r++] = n;
+       /* sort root list so components are contiguous */
+       qsort(rv.root,rv.n,sizeof(node_t*),ND_comp_cmpf);
+       return rv;
+}
+
+static void install(Agraph_t *g, Agnode_t *n)
+{
+       int                             rank;
+       rank_t          *r;
+
+       rank = ND_rank(n);
+       r = &GD_rank(g)[rank];
+       r->v[r->n] = n;
+       ND_order(n) = r->n++;
+}
+
+/* 
+ populates rank lists of g.  there are some key details:
+ 1) the input graph ordering must be respected (in left to right initialization)
+ 2) connected components are separated and marked with indices
+ 3) series-parallel graphs (includes trees, obviously) must not have crossings
+*/
+static void build_ranks(Agraph_t *g, boolean down)
+{
+       queue                   *q;
+       component_t c;
+       int                             r;
+       Agnode_t        *n;
+       Agedge_t        *e;
+
+       c = build_components(g, down);
+
+       /* process each each component */
+       q = new_queue(agnnodes(g)+1);
+       for (r = 0; r < c.r; r++) {
+               enqueue(q,c.root[r]);
+               if ((r + 1 >= c.r)||(ND_component(c.root[r])!=ND_component(c.root[r+1]))) {
+                       while ((n = dequeue(q))) {
+                               install(g,n);
+                                       if (down) {
+                                               for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                                                       if (--ND_priority(e->head) == 0) enqueue(q,e->head);
+                                       }
+                                       else {
+                                               for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                                                       if (--ND_priority(e->tail) == 0) enqueue(q,e->head);
+                                       }
+                       }
+               }
+       }
+       free_queue(q);
+}
+
+static boolean run(Agraph_t *mg)
+{
+       if (GD_pass(mg) > GD_maxpass(mg)) return FALSE;
+       if (GD_pass(mg) - GD_lastwin(mg) > GD_mintry(mg)) return FALSE;
+       GD_pass(mg) = GD_pass(mg) + 1;
+       return TRUE;
+}
+
+static void mincross_clust(Agraph_t *ug)
+{
+       Agraph_t        *g;
+       g = GD_model(ug);
+       if (run(g)) {
+               do {
+                       mincross_sweep(g,GD_pass(g)%2,GD_pass(g)%4<2);
+               } while (run(g));
+               transpose_sweep(g,TRUE);
+               restorebest(g);
+       }
+}
+
+static void globalopt(Agraph_t *root)
+{
+       Agraph_t        *g;
+       rank_t          *glob;
+
+       g = GD_model(root);
+       glob = globalize(root,g);
+       GD_rank(g) = glob;
+       fprintf(stderr,"%s: %d crossings\n",root->name,crossings(g));
+}
+
+/* this assumes one level per node - no mega-nodes */
+static void apply_model(Agraph_t *g)
+{
+       Agnode_t *n;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) 
+                       ND_order(n) = ND_order(ND_rep(n));
+}
+
+/* this is a first cut at a top-level planner.  it's lame. */
+static void rec_cluster_run(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) mincross_clust(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_run(subg);
+       if (is_a_cluster(ug)) mincross_clust(ug);
+}
+
+/* this is the top level mincross entry point */
+void dot_mincross(Agraph_t *user)
+{
+       rec_cluster_init(user);
+       rec_cluster_run(user);
+       globalopt(user);
+       apply_model(user);
+}
+
+static void invalidate(Agraph_t *g, int rank)
+{
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].crossing_cache.valid = FALSE;
+       if (rank > GD_minrank(g)) GD_rank(g)[rank-1].candidate = TRUE;
+       if (rank < GD_maxrank(g)) GD_rank(g)[rank+1].candidate = TRUE;
+}
+
+/* swaps two nodes in the same level */
+static void exchange(Agraph_t *g, Agnode_t *u, Agnode_t *v)
+{
+       rank_t  *r;
+       int                     ui,vi,rank;
+
+       assert(ND_rank(u) == ND_rank(v));
+       rank = ND_rank(u);
+       r = &GD_rank(g)[rank];
+       ui = ND_order(u);
+       vi = ND_order(v);
+       ND_order(v) = ui;
+       ND_order(u) = vi;
+       r->v[ND_order(u)] = u;
+       r->v[ND_order(v)] = v;
+       r->crossing_cache.valid = FALSE;
+       r->changed = TRUE;
+       r->candidate = TRUE;    /* old dot had this.  i have qualms. sn */
+       invalidate(g,rank);
+}
+
+int transpose_onerank(graph_t* g, int r, boolean reverse)
+{
+       int     i,c0,c1,rv;
+       node_t  *v,*w;
+
+       rv = 0;
+       GD_rank(g)[r].candidate = FALSE;
+       for (i = 0; i < GD_rank(g)[r].n - 1; i++) {
+               v = GD_rank(g)[r].v[i];
+               w = GD_rank(g)[r].v[i+1];
+               assert (ND_order(v) < ND_order(w));
+               if (left2right(g,v,w)) continue;
+               c0 = c1 = 0;
+               if (r > GD_minrank(g)) {
+                       c0 += in_cross(v,w);
+                       c1 += in_cross(w,v);
+               }
+               if (r < GD_maxrank(g)) {
+                       c0 += out_cross(v,w);
+                       c1 += out_cross(w,v);
+               }
+               if ((c1 < c0) || ((c0 > 0) && reverse && (c1 == c0))) {
+                       exchange(g,v,w);
+                       rv += (c0 - c1);
+               }
+       }
+       return rv;
+}
+
+static void transpose_sweep(graph_t* g, int reverse)
+{
+    int     r,delta;
+
+       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+               GD_rank(g)[r].candidate = TRUE;
+    do {
+                       delta = 0;
+                       for (r = GD_minrank(g); r <= GD_maxrank(g); r++)
+                               if (GD_rank(g)[r].candidate) delta += transpose_onerank(g,r,reverse);
+    }
+               while (delta >= 1);
+               /* while (delta > crossings(g)*(1.0 - Convergence));*/
+}
+
+static void mincross_sweep(graph_t* g, int dir, boolean reverse)
+{
+    int     r,other,low,high,first,last;
+    int     hasfixed;
+
+               low = GD_minrank(g);
+               high = GD_maxrank(g);
+               if (dir == 0) return;
+               if (dir > 0)  { first = low + 1; last = high; dir = 1;}         /* down */
+               else                              { first = high - 1; last = low; dir = -1;}    /* up */
+
+    for (r = first; r != last + dir; r += dir) {
+        other = r - dir;
+        hasfixed = medians(g,r,other);
+        reorder(g,r,reverse,hasfixed);
+    }
+    transpose_sweep(g,NOT(reverse));
+       savebest(g);
+}
+
+
+static int left2right(graph_t *g, node_t *v, node_t *w)
+{
+    int         rv;
+
+#ifdef NOTDEF
+    adjmatrix_t *M;
+    M = GD_rank(g)[ND_rank(v)].flat;
+    if (M == NULL) rv = FALSE;
+    else {
+        if (GD_flip(g)) {node_t *t = v; v = w; w = t;}
+        rv = ELT(M,flatindex(v),flatindex(w));
+    }
+#else
+               rv = FALSE;
+#endif
+    return rv;
+}
+
+static void build_flat_graphs(graph_t *g)
+{
+}
+
+static int out_cross(node_t *v, node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int  inv, cross = 0,t;
+
+  for (e2 = agfstout(w->graph,w); e2; e2 = agnxtout(w->graph,e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(e2->head);
+               for (e1 = agfstout(v->graph,v); e1; e1 = agnxtout(v->graph,e1)) {
+                       t = ND_order(e1->head) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_headport(e1).p.x > ED_headport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+                                                                                
+static int in_cross(node_t *v,node_t *w)
+{
+  register edge_t *e1,*e2;
+  register int inv, cross = 0, t;
+                                                                                
+  for (e2 = agfstin(w->graph,w); e2; e2 = agnxtin(w->graph,e2)) {
+               register int cnt = ED_xpenalty(e2);
+               inv = ND_order(e2->tail);
+               for (e1 = agfstin(v->graph,v); e1; e1 = agnxtin(v->graph,e1)) {
+                       t = ND_order(e1->tail) - inv;
+                       if ((t > 0) || ((t == 0) && (ED_tailport(e1).p.x > ED_tailport(e2).p.x)))
+                               cross += ED_xpenalty(e1) * cnt;
+    }
+       }
+       return cross;
+}
+
+static int int_cmpf(const void *arg0, const void *arg1)
+{
+       return *(int*)arg0 - *(int*)arg1;
+}
+
+
+/* 8 is the number of bits in port.order, an unsigned char */
+#define VAL(node,port) (((node)->u.order << 8)  + (port).order)
+
+/*
+ * defines ND_sortweight of each node in r0 w.r.t. r1
+ * returns...
+ */
+static boolean medians(graph_t *g, int r0, int r1)
+{
+       static int *list;
+       static int list_extent;
+       int     i,j,lm,rm,lspan,rspan;
+       node_t  *n,**v;
+       edge_t  *e;
+       boolean hasfixed = FALSE;
+
+       if (list_extent < GD_maxinoutdeg(g)) {
+               list_extent = GD_maxinoutdeg(g);
+               if (!list) list = realloc(list,sizeof(list[0])*list_extent);
+               else list = realloc(list,sizeof(list[0])*list_extent);
+       }
+       v = GD_rank(g)[r0].v;
+       for (i = 0; i < GD_rank(g)[r0].n; i++) {
+               n = v[i]; j = 0;
+               if (r1 > r0) for (e = agfstout(g,n); e; e = agnxtout(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(e->head,ED_headport(e));}
+               else for (e = agfstin(g,n); e; e = agnxtin(g,e))
+                       {if (ED_xpenalty(e) > 0) list[j++] = VAL(e->tail,ED_tailport(e));}
+               switch(j) {
+                       case 0:
+                               ND_sortweight(n) = -1;          /* no neighbor - median undefined */
+                               break;
+                       case 1:
+                               ND_sortweight(n) = list[0];
+                               break;
+                       case 2:
+                               ND_sortweight(n) = (list[0] + list[1])/2;
+                               break;
+                       default:
+                               qsort(list,j,sizeof(int),int_cmpf);
+                               if (j % 2) ND_sortweight(n) = list[j/2];
+                               else {
+                                       /* weighted median */
+                                       rm = j/2;
+                                       lm = rm - 1;
+                                       rspan = list[j-1] - list[rm];
+                                       lspan = list[lm] - list[0];
+                                       if (lspan == rspan)
+                                               ND_sortweight(n) = (list[lm] + list[rm])/2;
+                                       else {
+                                               int w = list[lm]*rspan + list[rm]*lspan;
+                                               ND_sortweight(n) = w / (lspan + rspan);
+                                       }
+                               }
+               }
+       }
+#ifdef NOTDEF
+       /* this code was in the old mincross */
+       for (i = 0; i < GD_rank(g)[r0].n; i++) {
+               n = v[i];
+               if ((ND_out(n).size == 0) && (ND_in(n).size == 0))
+                       hasfixed |= flat_sortweight(n);
+       }
+#endif
+       return hasfixed;
+}
+
+static void reorder(graph_t *g, int r, boolean reverse, boolean hasfixed)
+{
+       boolean changed, muststay;
+       node_t  **vlist, **lp, **rp, **ep;
+       int                     i;
+
+       changed = FALSE;
+       vlist = GD_rank(g)[r].v;
+       ep = &vlist[GD_rank(g)[r].n];
+       
+       for (i = 0; i < GD_rank(g)[r].n; i++) {
+               lp = &vlist[0];
+               /* find leftmost node that can be compared */
+               while ((lp < ep) && (ND_sortweight(*lp) < 0)) lp++;
+               if (lp >= ep) break;
+               /* find the node that can be compared */
+               muststay = FALSE;
+               for (rp = lp + 1; rp < ep; rp++) {
+                       if (left2right(g,*lp,*rp)) { muststay = TRUE; break; }
+                       if (ND_sortweight(*rp) >= 0) break;     /* weight defined; it's comparable */
+               }
+               if (rp >= ep) break;
+               if (muststay == FALSE) {
+                       register int    p1 = ND_sortweight(*lp);
+                       register int    p2 = ND_sortweight(*rp);
+                       if ((p1 > p2) || ((p1 == p2) && (reverse))) {
+                               exchange(g,*lp,*rp);
+                               changed = TRUE;
+                       }
+               }
+               lp = rp;
+               if ((hasfixed == FALSE) && (reverse == FALSE)) ep--;
+       }
+                                                                                
+       if (changed) {
+               GD_rank(g)[r].changed = TRUE;
+               GD_rank(g)[r].crossing_cache.valid = FALSE;
+               if (r > 0) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+               if (r + 1 > GD_rank(g)[r+1].n) GD_rank(g)[r-1].crossing_cache.valid = FALSE;
+       }
+}
+
+static void savebest(graph_t *g)
+{
+       int             nc;
+       Agnode_t        *n;
+
+       nc = crossings(g);
+       if (nc < GD_bestcrossings(g)) {
+               for (n = agfstnode(g); n; n = agnxtnode(g,n))
+                       ND_saveorder(n) = ND_order(n);
+               GD_bestcrossings(g) = nc;
+               GD_lastwin(g) = GD_pass(g);
+       }
+}
+
+static int ND_order_cmpf(const void *arg0, const void *arg1)
+{
+       return ND_order(*(Agnode_t**)arg0) - ND_order(*(Agnode_t**)arg1);
+}
+
+static void restorebest(graph_t *g)
+{
+       Agnode_t        *n;
+       int                     i;
+
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++) 
+               GD_rank(g)[i].changed = FALSE;
+       for (n = agfstnode(g); n; n = agnxtnode(g,n))  {
+               if (ND_order(n) != ND_saveorder(n)) {
+                       invalidate(g,ND_rank(n));
+                       GD_rank(g)[i].changed = TRUE;
+                       ND_order(n) = ND_saveorder(n);
+               }
+       }
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++)  {
+               if (GD_rank(g)[i].changed)
+                       qsort(GD_rank(g)[i].v,GD_rank(g)[i].n,sizeof(Agnode_t*),ND_order_cmpf);
+       }
+}
+
+static int crossings(graph_t *g)
+{
+               int             i, rv;
+
+               rv = 0;
+               for (i = GD_minrank(g); i < GD_maxrank(g); i++) {
+                       rv += crossings_below(g,i);
+               }
+               return rv;
+}
+
+/* returns level of (model) node's cluster */
+static int lev(Agnode_t *n)
+{
+       if (!n) return -1;
+       return (GD_level(ND_cluster(n)));
+}
+
+/* 
+ * allocates a vpath_t per original user edge.  later the path
+ * contents will be set to define vnode chains of intercluster edges.
+ */
+static void interclusterpaths(Agraph_t *ug, Agraph_t *topmodel)
+{
+       Agnode_t        *n, *n0;
+       Agedge_t        *e, *ue;
+       vpath_t         *p;
+
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(ug,n); e; e = agnxtout(ug,e)) {
+                       if (ND_cluster(e->tail) != ND_cluster(e->head)) {
+                               if (!(p = pathsearch(ug,e->tail,e->head))) {
+                                       p = newpath(e->tail, e->head);
+                                       pathinsert(topmodel->root,p);
+                               }
+                       }
+               }
+       }
+
+       /* scan all vnodes of model graph and make decisions */
+       for (n = agfstnode(topmodel); n; n = agnxtnode(topmodel,n)) {
+               if (ND_type(n) == NODETYPE_VNODE) {
+                       ue = ND_erep(n);
+                       p = pathsearch(topmodel->root,ue->tail,ue->head);
+                       if (p) {
+                               if (!(n0 = p->v[ND_rank(n)]) || (lev(n0) < lev(n)))
+                                       p->v[ND_rank(n)] = n;
+                       }
+               }
+       }
+       /* need to remove the other edges of these vnodes!! */
+}
+
+/* build the global (flat) graph of the universe.  this is 'flat'
+in the sense that there is one data structure for the entire graph
+(not 'flat' in the sense of flat edges within the same level.)
+*/
+static rank_t *globalize(Agraph_t *user, Agraph_t *topmodel)
+{
+       rank_t  *globrank;
+       int             minr,maxr,r;
+
+       /* setup bookkeeping */
+       interclusterpaths(user, topmodel);
+
+       /* allocate global ranks */
+       minr = GD_minrank(topmodel);
+       maxr = GD_maxrank(topmodel);
+       globrank = T_array(minr,maxr,sizeof(rank_t));
+       countup(user,globrank);
+       for (r = minr; r <= maxr; r++) {
+               globrank[r].v = N_NEW(globrank[r].n+1,Agnode_t*);       /* NIL at end */
+               globrank[r].n = 0;      /* reset it */
+       }
+
+       /* installation */
+       for (r = minr; r <= maxr; r++)
+               installglob(user,topmodel,globrank,r);
+
+       removejunk(user, topmodel);
+       reconnect(user, topmodel);
+
+       /* optimization */
+       return globrank;
+}
+
+
+static void countup(Agraph_t *g, rank_t *globr)
+{
+       Agnode_t        *n;
+       Agedge_t        *e;
+       int                             r0, r1, low, high, i;
+
+       for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
+                       globr[ND_rank(n)].n += 1;
+                       for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
+                               r0 = ND_rank(e->tail);
+                               r1 = ND_rank(e->head);
+                               low = MIN(r0,r1);
+                               high = MAX(r0,r1);
+                               for (i = low + 1; i < high; i++)
+                                       globr[i].n += 1;
+                       }
+       }
+}
+
+/* install nodes from rank r of (g or its clusters) into globr. */
+static void installglob(Agraph_t *ug, Agraph_t *fromgraph, rank_t *globr, int r)
+{
+       rank_t          *myrank;
+       int                             i;
+       Agnode_t        *v;
+       Agedge_t        *uedge;
+       vpath_t         *path;
+
+       if (is_a_cluster(fromgraph)) {
+               myrank = &GD_rank(fromgraph)[r];
+               i = 0;
+               while ((v = myrank->v[i++])) {
+                       switch (ND_type(v)) {
+                               case NODETYPE_REAL:
+                                               /* install primitive nodes */
+                                       globr[r].v[globr[r].n++] = v;
+                                       ND_isglobal(v) = TRUE;
+                                       break;
+                               case NODETYPE_VNODE:
+                                       /* install vnode for non-intercluster edges, and for intercluster
+                                                edges if this vnode was chosen as the path representative */
+                                       uedge = ND_erep(v);
+                                       path = pathsearch(fromgraph->root,uedge->tail,uedge->head);
+                                       if (!path || (path->v[r] == v)) {
+                                               globr[r].v[globr[r].n++] = v;
+                                               ND_isglobal(v) = TRUE;
+                                       }
+                                       break;
+                               case NODETYPE_CNODE:
+                                       /* install clusters recursively */
+                                       installglob(ug,ND_cluster(v),globr,r);
+                                       break;
+                               case NODETYPE_XNODE:
+                                       /* we're ignoring these */
+                                       break;
+                       }
+               }
+       }
+}
+
+/*
+       after making the global graph, delete all non-global objects.
+ */
+static void removejunk(Agraph_t *ug, Agraph_t *topmodel)
+{
+       Agnode_t        *v,*vv;
+
+       for (v = agfstnode(topmodel); v; v = vv) {
+               vv = agnxtnode(topmodel,v);
+               if (!ND_isglobal(v)) agdelete(topmodel,v);
+       }
+}
+
+/* 
+ fix up the user edge paths in the global graph.
+*/
+static void reconnect(Agraph_t *ug, Agraph_t *topmodel)
+{
+       Agnode_t        *n, *n0, *n1;
+       Agedge_t        *e, *e0;
+       vpath_t         *p;
+       int                             i;
+
+       for (n = agfstnode(ug); n; n = agnxtnode(ug,n)) {
+               for (e = agfstout(ug,n); e; e = agnxtout(ug,e)) {
+                       if (ND_cluster(e->tail) != ND_cluster(e->head)) {
+                               p = pathsearch(ug,e->tail,e->head);
+                               n0 = p->v[p->low];
+                               for (i = p->low + 1; i <= p->high; i++) {
+                                       n1 = p->v[i];
+                                       if (!(e0 = p->e[i])) e0 = agedge(topmodel,n0,n1);
+                                       ED_weight(e0) += ED_weight(e);
+                                       ED_xpenalty(e0) += ED_xpenalty(e);
+                                       n0 = n1;
+                               }
+                       }
+               }
+       }
+}
+
+#ifdef NOTDEF
+/* (no parallel edges, all forward edges, flat cycles broken) */
+void build_main_graph(Agraph_t *g)
+{
+       Agraph_t        *gprime,*gprimeflat;
+       Agnode_t        *v,*vprime;
+       Agedge_t        *eprev;
+
+       gprime = agopen("model",AGDIGRAPHSTRICT);
+       for (v = agfstnode(g); v; v = agnxtnode(g,v)) {
+                       vprime = agnode(gprime,v->name);
+                       for (e = agfstout(v); e; e = agxntout(g,e)) {
+                               tail = e->tail;
+                               head = e->head;
+                               if (ND_rank(tail) > ND_rank(head)) {Agnode_t *t = head; head = tail; tail = t;}
+                               if (!(eprev = agfindedge(gprime,tail,head))) eprev = agedge(gprime,tail,head);
+                               merge(eprev,e);
+                               associate(e,eprev);
+                       }
+       }
+       break_cycles(gprime);
+}
+#endif
+
+/* I. initialization */
+static void cluster_init(Agraph_t *ug)
+
+{
+       Agraph_t        *mg;
+       scangraph(ug);
+       mg = cluster_model(ug);
+       flat_edges(mg);
+       build_ranks(mg,TRUE);
+}
+
+static void rec_cluster_init(Agraph_t *ug)
+{
+       Agraph_t        *subg;
+
+       if (is_a_cluster(ug)) cluster_init(ug);
+       for (subg = agfstsubg(ug); subg; subg = agnxtsubg(ug,subg))
+               rec_cluster_init(subg);
+}
diff --git a/lib/dotgen2/t1.c b/lib/dotgen2/t1.c
new file mode 100644 (file)
index 0000000..491bffd
--- /dev/null
@@ -0,0 +1,8 @@
+#include <vmalloc.h>
+main()
+{
+       Vmalloc_t       *region;
+
+       region = vmmopen("/tmp/north123",0,1024*1024);
+       printf("%x\n",malloc(100));
+}
diff --git a/lib/dotgen2/t2.c b/lib/dotgen2/t2.c
new file mode 100644 (file)
index 0000000..b4c788f
--- /dev/null
@@ -0,0 +1,5 @@
+main()
+{
+
+       printf("%x\n",sbrk(0));
+}
diff --git a/lib/dotgen2/timing.c b/lib/dotgen2/timing.c
new file mode 100644 (file)
index 0000000..a0ebcf8
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef MSWIN32
+
+//#include    <unistd.h>
+#include       <sys/types.h>
+#include       <sys/times.h>
+#include       <sys/param.h>
+#ifndef HZ
+#define HZ 60
+#endif
+typedef struct tms mytime_t;
+#define GET_TIME(S) times(&(S))
+#define DIFF_IN_SECS(S,T) ((S.tms_utime + S.tms_stime - T.tms_utime - T.tms_stime)/(double)HZ)
+
+#else
+
+#include       <time.h>
+typedef clock_t mytime_t;
+#define GET_TIME(S) S = clock()
+#define DIFF_IN_SECS(S,T) ((S - T) / (double)CLOCKS_PER_SEC)
+
+#endif
+       
+
+static mytime_t T;
+
+void
+start_timer(void)
+{
+       GET_TIME(T);
+}
+
+double
+elapsed_sec(void)
+{
+       mytime_t        S;
+       double          rv;
+
+       GET_TIME(S);
+       rv = DIFF_IN_SECS(S,T);
+       return rv;
+}
diff --git a/lib/dotgen2/trysort.c b/lib/dotgen2/trysort.c
new file mode 100644 (file)
index 0000000..f81d7c1
--- /dev/null
@@ -0,0 +1,16 @@
+#include "newdot.h"
+
+typedef struct rsort_s {
+       unsigned char key[5];
+       Agedge_t                *e;
+} rsort_t;
+
+main()
+{
+       Agraph_t        *g;
+       Agnode_t        *n;
+       Agedge_t        *e, **edgelist;
+       g = agread(stdin);
+       edgelist = alledges(g);
+       radixsort(edgelist, agnedges(g), 0, endchar);
+}
diff --git a/lib/dotgen2/xcoord.c b/lib/dotgen2/xcoord.c
new file mode 100644 (file)
index 0000000..2818b1b
--- /dev/null
@@ -0,0 +1,15 @@
+void dot_X(Agraph_t *ug)
+{
+       graph_t         *Xg = agopen(AGSTRICTDIRECTED,"constraints");
+
+       make_nodevars(ug,Xg);
+       make_edgevars(ug,Xg);
+       make_clustvars(ug,Xg);
+       constrain_nodenode(ug,Xg);
+       constrain_edgelen(ug,Xg);
+       constraint_nodeclust(ug,Xg);
+       constraint_clustclust(ug,Xg);
+       solve();
+       read_nodevars(ug,Xg);
+       read_clustvars(ug,Xg);
+}
diff --git a/lib/dotgen2/xpos.c b/lib/dotgen2/xpos.c
new file mode 100644 (file)
index 0000000..cd8f84b
--- /dev/null
@@ -0,0 +1,135 @@
+#include "newdot.h"
+
+static Agraph_t *constraint_graph(Agraph_t *model);
+static void node_separation_constraints(Agraph_t *user);
+static void cluster_containment_constraints(Agraph_t *upar, Agraph_t *ug);
+static void edge_cost_constraints(Agraph_t *user);
+
+void dot_position(Agraph_t *user)
+{
+       Agraph_t        *cg;
+       cg = constraint_graph(user);
+       node_separation_constraints(user);
+       cluster_containment_constraints(user);
+       edge_cost_constraints(user);
+       rank(cg);
+       readout(user);
+       agclose(cg);
+}
+
+static Agraph_t *constraint_graph(Agraph_t *model)
+{
+       if (!GD_model(model->root))
+               GD_model(model->root) = agopen("Xconstraint",AGDIGRAPHSTRICT);
+       return GD_model(model->root);
+}
+
+static void node_sep(Agraph_t *g, Agnode_t *left, Agnode_t *right)
+{
+       Agraph_t        *Xg;
+       Agnode_t_       *leftvar; *rightvar;
+       double  d0, d1;
+
+       d0 = ND_xsize(left) / 2.0;
+       d1 = ND_xsize(right) / 2.0;
+       /* what about self edge sizes */
+       Xg = constraint_graph(g);
+       leftvar = nodevar(left);
+       rightvar = nodevar(right);
+       e = agedge(Xg, leftvar, rightvar);
+       ED_weight(e) = 0;
+       ED_minlen(e) = XSCALE(d0 + d1);
+}
+
+/* g is a model graph */
+static void node_separation_constraints(Agraph_t *user)
+{
+       Agraph_t        *g;
+       rank_t  *r;
+
+       g = GD_model(user);
+       for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
+               r = GD_rank(g)[i];
+               left = NILnode;
+               for (j = leftmost(g,r); j <= rightmost(g,r); j++) {
+                       right = r->v[j];
+                       node_sep(g,left,right);
+                       right = left;
+               }
+       }
+}
+
+/*
+ * this function has to traverse the subgraph hierarchy from the
+ * user graph because we flattened out the model subgraphs.
+ */
+static void cluster_containment_constraints(Agraph_t *upar, Agraph_t *ug)
+{
+       Agraph_t        *container;
+
+       if (is_a_cluster(upar) && is_a_cluster(ug))
+               cluster_contain(upar,ug);
+       container = is_a_cluster(ug)? ug : upar;
+       for (subg = agfstsub(ug); subg; subg = agnxtsubg(ug,subg))
+               cluster_containment_constraints(container,subg);
+}
+
+static void cluster_contain(Agraph_t *u_outer, Agraph_t *u_inner)
+{
+       Agraph_t        *Xg;
+       int             sep;
+       Agnode_t        *outer_left, *outer_right, *inner_left, *inner_right;
+
+       sep = user_cluster_sep(u_inner);
+       Xg = constraint_graph(GD_model(u_outer));
+       outer_left = leftvar(GD_model(u_outer));
+       outer_right = rightvar(GD_model(u_outer));
+       inner_left = leftvar(GD_model(u_inner));
+       inner_right = rightvar(GD_model(u_inner));
+       clust_sep_edge(outer_left,inner_left);
+       clust_sep_edge(inner_right,outer_right);
+}
+
+static void clust_sep_edge(Agraph_t *Xg, Agnode_t *left, Agnode_t *right, int sep)
+{
+       Agedge_t        *e;
+
+       e = agedge(Xg, left, right);
+       ED_weight(e) = 0;
+       ED_minlen(e) = sep;
+}
+
+static int vedge_cost(int r, int low, int high)
+{
+       if (low + 1 == high) return SHORT_FACTOR;
+       if ((r == low) || (r == high)) return LONG_END_FACTOR;
+       return LONG_FACTOR;
+}
+
+static void edge_cost_constraints(Agraph_t *user)
+{
+       Agraph_t *model;
+       Agraph_t *Xg;           /* constraints */
+       Dict_t  *dict;  /* set of paths in the model graph */
+       vpath_t *p;
+
+       model = GD_model(user);
+       Xg = constraint_graph(model);
+       dict = GD_pathdict(model);
+       for (p = dtfirst(dict); p; p = dtnext(dict,p)) {
+               v0 = nodevar(p->v[p->low]);
+               for (i = p->low; i < p->high; i++) {
+                       v1 = nodevar(p->v[i+1]);
+                       vx = agnode(Xg,0);
+                       e0 = agedge(Xg,vx,v0);
+                       e1 = agedge(Xg,vx,v1);
+                       ED_weight(e0) = ED_weight(e1) = p->weight * vedge_factor(i,low,high);
+
+                       tp = (i == p->low?) p->tailport : 0;
+                       hp = (i == p->high - 1?) p->headport : 0;
+                 m0 = hp - tp;
+                       if (m0 > 0) ED_minlen(e0) = m0;
+                       else ED_minlen(e1) = -m0; 
+               }
+       }
+}