From: arif Date: Tue, 19 Jan 2010 16:36:09 +0000 (+0000) Subject: experimental new dot core X-Git-Tag: LAST_LIBGRAPH~32^2~1478 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2a57a6ae742a03f94d80e15fc3de3df66be261f7;p=graphviz experimental new dot core --- diff --git a/lib/dotgen2/acyclic.c b/lib/dotgen2/acyclic.c new file mode 100644 index 000000000..78dc96274 --- /dev/null +++ b/lib/dotgen2/acyclic.c @@ -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 index 000000000..7f97eda04 --- /dev/null +++ b/lib/dotgen2/au_.c @@ -0,0 +1,80 @@ +#include "newdot.h" +#include + +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 index 000000000..2e595cdb6 --- /dev/null +++ b/lib/dotgen2/au_.h @@ -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 index 000000000..f03df860d --- /dev/null +++ b/lib/dotgen2/bjm.c @@ -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 +#include +#include + +#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 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 index 000000000..4bbabafd2 --- /dev/null +++ b/lib/dotgen2/bjm0.c @@ -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 +#include +#include + +#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 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 index 000000000..7e66b5176 --- /dev/null +++ b/lib/dotgen2/ce.c @@ -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 index 000000000..87808ff5b --- /dev/null +++ b/lib/dotgen2/countx.c @@ -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; ttreesize; 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 index 000000000..e232ba087 --- /dev/null +++ b/lib/dotgen2/flat.c @@ -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 index 000000000..5e55e3a83 --- /dev/null +++ b/lib/dotgen2/level.c @@ -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 index 000000000..b36d0bed0 --- /dev/null +++ b/lib/dotgen2/main.c @@ -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 index 000000000..cdf83e6f9 --- /dev/null +++ b/lib/dotgen2/minc.c @@ -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 index 000000000..16072aea1 --- /dev/null +++ b/lib/dotgen2/newdot.h @@ -0,0 +1,287 @@ +/*********** external interfaces *********/ +#ifdef NOTDEF +#include +#endif +#include +#include +//#include +#include +#include +/* #include */ +#include +#include +#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 index 000000000..1abaeeb71 --- /dev/null +++ b/lib/dotgen2/ns.c @@ -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 index 000000000..f40fc06e3 --- /dev/null +++ b/lib/dotgen2/pos.c @@ -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 index 000000000..1eda3cadb --- /dev/null +++ b/lib/dotgen2/queue.c @@ -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 index 000000000..3e8ee3424 --- /dev/null +++ b/lib/dotgen2/radix.c @@ -0,0 +1,119 @@ +/* +From: Andre Reinald +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 +#include +#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 index 000000000..e39fae206 --- /dev/null +++ b/lib/dotgen2/radix.h @@ -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 index 000000000..3b4aa3529 --- /dev/null +++ b/lib/dotgen2/radix0.c @@ -0,0 +1,95 @@ +#include +#include + +// 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 index 000000000..911d20e31 --- /dev/null +++ b/lib/dotgen2/radix1.c @@ -0,0 +1,105 @@ +/* + http://www.cubic.org/~submissive/sourcerer/radix.htm + http://www.cubic.org/~submissive/sourcerer/download/radix_ar_2001.c +*/ + +#include +#include + +// 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 index 000000000..93cb37e0b --- /dev/null +++ b/lib/dotgen2/save2_minc.c @@ -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 index 000000000..5902c3f0e --- /dev/null +++ b/lib/dotgen2/save_minc.c @@ -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 index 000000000..2ae497c0b --- /dev/null +++ b/lib/dotgen2/t.c @@ -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 index 000000000..491bffd73 --- /dev/null +++ b/lib/dotgen2/t1.c @@ -0,0 +1,8 @@ +#include +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 index 000000000..b4c788f9e --- /dev/null +++ b/lib/dotgen2/t2.c @@ -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 index 000000000..a0ebcf809 --- /dev/null +++ b/lib/dotgen2/timing.c @@ -0,0 +1,41 @@ +#ifndef MSWIN32 + +//#include +#include +#include +#include +#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 +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 index 000000000..f81d7c1e4 --- /dev/null +++ b/lib/dotgen2/trysort.c @@ -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 index 000000000..2818b1b75 --- /dev/null +++ b/lib/dotgen2/xcoord.c @@ -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 index 000000000..cd8f84b4d --- /dev/null +++ b/lib/dotgen2/xpos.c @@ -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; + } + } +}