/* This software may only be used by you under license from AT&T Corp. ("AT&T"). A copy of AT&T's Source Code Agreement is available at AT&T's Internet website having the URL: If you received this software without first entering into a license with AT&T, you have an infringing copy of this software and cannot use it without violating AT&T's intellectual property rights. */ #pragma prototyped #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "neato.h" #include #ifndef MSWIN32 #include #endif #ifndef HAVE_SRAND48 #define srand48 srand #endif static double Epsilon2; #ifndef HAVE_DRAND48 double drand48(void) { double d; d = rand(); d = d / RAND_MAX; return d; } #endif double fpow32(double x) { x = sqrt(x); return x * x * x; } double distvec(double *p0, double *p1, double *vec) { int k; double dist = 0.0; for (k = 0 ; k < Ndim ; k++) { vec[k] = p0[k] - p1[k]; dist += (vec[k]*vec[k]); } dist = sqrt(dist); return dist; } double **new_array(int m, int n, double ival) { double **rv; double *mem; int i,j; rv = N_NEW(m,double*); mem = N_NEW(m*n,double); for (i = 0; i < m; i++) { rv[i] = mem; mem = mem + n; for (j = 0; j < n; j++) rv[i][j] = ival; } return rv; } void free_array(double **rv) { free(rv[0]); free(rv); } static double ***new_3array(int m, int n, int p, double ival) { double ***rv; int i,j,k; rv = N_NEW(m+1,double**); for (i = 0; i < m; i++) { rv[i] = N_NEW(n+1,double*); for (j = 0; j < n; j++) { rv[i][j] = N_NEW(p,double); for (k = 0; k < p; k++) rv[i][j][k] = ival; } rv[i][j] = NULL; /* NULL terminate so we can clean up */ } rv[i] = NULL; return rv; } static void free_3array(double ***rv) { int i,j; for (i = 0; rv[i]; i++) { for (j = 0; rv[i][j]; j++) free(rv[i][j]); free(rv[i]); } free(rv); } double doubleattr(void* obj, int index, double defval) { double val; if (index < 0) return defval; if (sscanf(agxget(obj,index),"%lf",&val) < 1) return defval; return val; } /* degreeKind; * Returns degree of n ignoring loops and multiedges. * Returns 0, 1 or many (2) * For case of 1, returns other endpoint of edge. */ static int degreeKind (graph_t* g, node_t* n, node_t** op) { edge_t* ep; int deg = 0; node_t* other = NULL; for (ep = agfstedge(g,n); ep; ep = agnxtedge(g,ep,n)) { if (ep->head == ep->tail) continue; /* ignore loops */ if (deg == 1) { if (((ep->tail == n) && (ep->head == other)) || /* ignore multiedge */ ((ep->tail == other) && (ep->head == n))) continue; return 2; } else { /* deg == 0 */ if (ep->tail == n) other = ep->head; else other = ep->tail; *op = other; deg++; } } return deg; } /* prune: * np is at end of a chain. If its degree is 0, remove it. * If its degree is 1, remove it and recurse. * If its degree > 1, stop. * The node next is the next node to be visited during iteration. * If it is equal to a node being deleted, set it to next one. * Return next. */ static node_t* prune(graph_t *G, node_t* np, node_t* next) { node_t* other; int deg; while (np) { deg = degreeKind (G,np,&other); if (deg == 0) { if (next == np) next = agnxtnode(G,np); agdelete(G,np); np = 0; } else if (deg == 1) { if (next == np) next = agnxtnode(G,np); agdelete(G,np); np = other; } else np = 0; } return next; } int scan_graph(graph_t *G) { int i,lenx,nV,nE,deg; char *str; double len; node_t *np,*xp,*other; edge_t *ep; double total_len = 0.0; if (Verbose) fprintf (stderr, "Scanning graph %s\n", G->name); /* Eliminate singletons and trees */ if (Reduce) { for (np = agfstnode(G); np; np = xp) { xp = agnxtnode(G,np); deg = degreeKind (G,np,&other); if (deg == 0) { /* singleton node */ agdelete (G,np); } else if (deg == 1) { agdelete (G,np); xp = prune(G,other,xp); } } } nV = agnnodes(G); nE = agnedges(G); if ((str = agget(G->root,"maxiter"))) MaxIter = atoi(str); else MaxIter = MAXINT; if ((str = agget(G->root,"Damping"))) Damping = atof(str); else Damping = .99; lenx = agindex(G->root->proto->e,"len"); GD_neato_nlist(G) = N_NEW(nV + 1,node_t*); for (i = 0, np = agfstnode(G); np; np = agnxtnode(G,np)) { GD_neato_nlist(G)[i] = np; ND_id(np) = i++; ND_heapindex(np) = -1; for (ep = agfstout(G,np); ep; ep = agnxtout(G,ep)) { char *str; str = agget(ep,"label"); if (str && str[0]) ND_has_edge_labels(G->root) = TRUE; len = doubleattr(ep,lenx,1.0); if (len <= 0) { agerr(AGERR, "bad edge len %f in %s ignored\n",len, G->name); len = 1.0; } ED_dist(ep) = len; total_len += len; } } Initial_dist = total_len / (nE > 0? nE : 1) * sqrt(nV) + 1; if (!Nop) { GD_dist(G) = new_array(nV,nV,Initial_dist); GD_spring(G) = new_array(nV,nV,1.0); GD_sum_t(G) = new_array(nV,Ndim,1.0); GD_t(G) = new_3array(nV,nV,Ndim,0.0); } return nV; } void free_scan_graph(graph_t *g) { /* int nG; */ free(GD_neato_nlist(g)); if (!Nop) { free_array(GD_dist(g)); free_array(GD_spring(g)); free_array(GD_sum_t(g)); /* nG = agnnodes(g); */ free_3array(GD_t(g)); GD_t(g) = NULL; } } void jitter3d(node_t* np, int nG) { int k; for (k = 2; k < Ndim; k++) ND_pos(np)[k] = nG*drand48(); } void randompos(node_t* np, int nG) { ND_pos(np)[0] = nG * drand48(); ND_pos(np)[1] = nG * drand48(); if (Ndim > 2) jitter3d(np, nG); } int user_pos(attrsym_t* symptr, node_t* np, int nG) { double *pvec; char *p,c; if (symptr == NULL) return FALSE; pvec = ND_pos(np); p = agxget(np,symptr->index); if (p[0]) { c = '\0'; if (sscanf(p,"%lf,%lf%c",pvec,pvec+1,&c) >= 2) { if (PSinputscale > 0.0) { int i; for (i = 0; i < Ndim; i++) pvec[i] = pvec[i] / PSinputscale; } if (Ndim > 2) jitter3d(np, nG); if ((c == '!') || (N_pin && mapbool(agxget(np,N_pin->index)))) ND_pinned(np) = TRUE; return TRUE; } else agerr(AGERR, "node %s, position %s, expected two doubles\n", np->name,p); } return FALSE; } void initial_positions(graph_t *G, int nG) { int i; unsigned seed; double a,da; node_t *np; attrsym_t *symptr; char *p,smallbuf[32]; if (Verbose) fprintf (stderr, "Setting initial positions\n"); N_pin = agfindattr(G->proto->n,"pin"); symptr = agfindattr(G->proto->n,"pos"); seed = 1; if ((p = agget(G,"start"))) { if (sscanf(p,"%d",&seed) < 1) { if (!strcmp(p,"regular")) { a = 0.0; da = (2 * PI) / nG; for (i = 0; (np = GD_neato_nlist(G)[i]); i++) { if (user_pos(symptr,np,nG)) continue; ND_pos(np)[0] = nG * Spring_coeff * cos(a); ND_pos(np)[1] = nG * Spring_coeff * sin(a); a = a + da; if (Ndim > 2) jitter3d(np, nG); } return; } else { #ifdef MSWIN32 seed = (unsigned) time(NULL); #else seed = (unsigned) getpid() ^ (unsigned) time(NULL); #endif sprintf(smallbuf,"%u",seed); agset(G,"start",smallbuf); } } } srand48(seed); for (i = 0; (np = GD_neato_nlist(G)[i]); i++) { if (user_pos(symptr,np,nG)) continue; randompos(np, nG); } } void diffeq_model(graph_t *G, int nG) { int i,j,k; double dist,**D,**K,del[MAXDIM],f; node_t *vi,*vj; edge_t *e; if (Verbose) fprintf (stderr, "Setting up spring model\n"); /* init springs */ K = GD_spring(G); D = GD_dist(G); for (i = 0; i < nG; i++) { for (j = 0; j < i; j++) { f = Spring_coeff / (D[i][j]*D[i][j]); if ((e = agfindedge(G,GD_neato_nlist(G)[i],GD_neato_nlist(G)[j]))) f = f * ED_factor(e); K[i][j] = K[j][i] = f; } } /* init differential equation solver */ for (i = 0; i < nG; i++) for (k = 0 ; k < Ndim ; k++) GD_sum_t(G)[i][k] = 0.0 ; for (i = 0; (vi = GD_neato_nlist(G)[i]); i++) { for (j = 0; j < nG; j++) { if (i == j) continue; vj = GD_neato_nlist(G)[j]; dist = distvec(ND_pos(vi),ND_pos(vj),del); for (k = 0 ; k < Ndim ; k++) { GD_t(G)[i][j][k] = GD_spring(G)[i][j]*(del[k] - GD_dist(G)[i][j]*del[k]/dist); GD_sum_t(G)[i][k] += GD_t(G)[i][j][k]; } } } } void solve_model(graph_t *G, int nG) { node_t *np; Epsilon2 = Epsilon * Epsilon; if (Verbose) fprintf (stderr, "Solving model\n"); while ((np = choose_node(G, nG))) { move_node(G, nG, np); } } void update_arrays(graph_t* G, int nG, int i) { int j,k; double del[MAXDIM],dist,old; node_t *vi,*vj; vi = GD_neato_nlist(G)[i]; for (k = 0 ; k < Ndim ; k++ ) GD_sum_t(G)[i][k] = 0.0; for (j = 0; j < nG; j++) { if (i == j) continue; vj = GD_neato_nlist(G)[j]; dist = distvec(ND_pos(vi),ND_pos(vj),del); for (k = 0 ; k < Ndim ; k++) { old = GD_t(G)[i][j][k]; GD_t(G)[i][j][k] = GD_spring(G)[i][j]*(del[k] - GD_dist(G)[i][j]*del[k]/dist); GD_sum_t(G)[i][k] += GD_t(G)[i][j][k]; old = GD_t(G)[j][i][k]; GD_t(G)[j][i][k] = - GD_t(G)[i][j][k]; GD_sum_t(G)[j][k] += (GD_t(G)[j][i][k] - old); } } } #define Msub(i,j) M[(i)*Ndim+(j)] void D2E(graph_t* G, int nG, int n, double *M) { int i,l,k; node_t *vi,*vn; double scale,sq, t[MAXDIM]; double **K = GD_spring(G); double **D = GD_dist(G); vn = GD_neato_nlist(G)[n]; for (l = 0 ; l < Ndim ; l++) for (k = 0 ; k < Ndim ; k++) Msub(l,k) = 0.0; for (i = 0; i < nG; i++) { if (n == i) continue; vi = GD_neato_nlist(G)[i]; sq = 0.0; for (k = 0 ; k < Ndim ; k++) { t[k] = ND_pos(vn)[k] - ND_pos(vi)[k]; sq += (t[k]*t[k]); } scale = 1 / fpow32(sq); for (k = 0 ; k < Ndim ; k++) { for (l = 0 ; l < k ; l++) Msub(l,k) += K[n][i] * D[n][i]*t[k]*t[l]*scale; Msub(k,k) += K[n][i] *(1.0 - D[n][i]*(sq-(t[k]*t[k]))*scale); } } for (k = 1 ; k < Ndim ; k++) for (l = 0 ; l < k ; l++) Msub(k,l) = Msub(l,k); } node_t * choose_node(graph_t *G, int nG) { int i,k; double m,max; node_t *choice,*np; static int cnt = 0; if (GD_move(G) >= MaxIter) return NULL; max = 0.0; choice = NULL; for (i = 0; i < nG; i++) { np = GD_neato_nlist(G)[i]; if (ND_pinned(np)) continue; for (m = 0.0, k = 0 ; k < Ndim ; k++ ) m += (GD_sum_t(G)[i][k] * GD_sum_t(G)[i][k]); /* could set the color=energy of the node here */ if (m > max) {choice = np; max = m;} } if (max < Epsilon2) choice = NULL; else { if (Verbose && (++cnt % 100 == 0)) { fprintf(stderr,"%.3f ",sqrt(max)); if (cnt % 1000 == 0) fprintf(stderr,"\n"); } } return choice; } void move_node(graph_t *G, int nG, node_t* n) { int i,m; static double *a,b[MAXDIM],c[MAXDIM]; m = ND_id(n); a = ALLOC(Ndim * Ndim, a, double); D2E(G,nG,m,a); for (i = 0 ; i < Ndim ; i++) c[i] = - GD_sum_t(G)[m][i]; solve(a,b,c,Ndim); for (i = 0 ; i < Ndim ; i++) { b[i] = (Damping + 2*(1-Damping)*drand48())* b[i]; ND_pos(n)[i] += b[i]; } GD_move(G)++; update_arrays(G,nG,m); if (test_toggle()) { double sum=0; for (i=0 ; i < Ndim ; i++) {sum += fabs(b[i]); } /* Why not squared? */ sum = sqrt(sum); fprintf(stderr,"%s %.3f\n",n->name,sum); } } static node_t **Heap; static int Heapsize; static node_t *Src; void heapup(node_t* v) { int i,par; node_t *u; for (i = ND_heapindex(v); i > 0; i = par) { par = (i - 1) / 2; u = Heap[par]; if (ND_dist(u) <= ND_dist(v)) break; Heap[par] = v; ND_heapindex(v) = par; Heap[i] = u; ND_heapindex(u) = i; } } void heapdown(node_t* v) { int i,left,right,c; node_t *u; i = ND_heapindex(v); while ((left = 2 * i + 1) < Heapsize) { right = left + 1; if ((right < Heapsize) && (Heap[right]->u.dist < Heap[left]->u.dist)) c = right; else c = left; u = Heap[c]; if (ND_dist(v) <= ND_dist(u)) break; Heap[c] = v; ND_heapindex(v) = c; Heap[i] = u; ND_heapindex(u) = i; i = c; } } void neato_enqueue(node_t* v) { int i; assert(ND_heapindex(v) < 0); i = Heapsize++; ND_heapindex(v) = i; Heap[i] = v; if (i > 0) heapup(v); } node_t * neato_dequeue(void) { int i; node_t *rv,*v; if (Heapsize == 0) return NULL; rv = Heap[0]; i = --Heapsize; v = Heap[i]; Heap[0] = v; ND_heapindex(v) = 0; if (i > 1) heapdown(v); ND_heapindex(rv) = -1; return rv; } void shortest_path(graph_t *G, int nG) { node_t *v; if (Verbose) fprintf (stderr, "Calculating shortest paths\n"); Heap = N_NEW(nG+1,node_t*); for (v = agfstnode(G); v; v = agnxtnode(G,v)) s1(G,v); free(Heap); } void s1(graph_t *G, node_t* node) { node_t *v,*u; edge_t *e; int t; double f; for (t = 0; (v = GD_neato_nlist(G)[t]); t++) ND_dist(v) = Initial_dist; Src = node; ND_dist(Src) = 0; ND_hops(Src) = 0; neato_enqueue(Src); while ((v = neato_dequeue())) { if (v != Src) make_spring(G, Src,v,ND_dist(v)); for (e = agfstedge(G,v); e; e = agnxtedge(G,e,v)) { if ((u = e->head) == v) u = e->tail; f = ND_dist(v) + ED_dist(e); if (ND_dist(u) > f) { ND_dist(u) = f; if (ND_heapindex(u) >= 0) heapup(u); else { ND_hops(u) = ND_hops(v) + 1; neato_enqueue(u); } } } } } void make_spring(graph_t *G, node_t *u, node_t *v, double f) { int i,j; i = ND_id(u); j = ND_id(v); GD_dist(G)[i][j] = GD_dist(G)[j][i] = f; } int allow_edits(int nsec) { #ifdef INTERACTIVE static int onetime = TRUE; static FILE *fp; static fd_set fd; static struct timeval tv; char buf[256],name[256]; double x,y; node_t *np; if (onetime) { fp = fopen("/dev/tty","r"); if (fp == NULL) exit(1); setbuf(fp,NULL); tv.tv_usec = 0; onetime = FALSE; } tv.tv_sec = nsec; FD_ZERO(&fd); FD_SET(fileno(fp),&fd); if (select(32,&fd,(fd_set*)0,(fd_set*)0,&tv) > 0) { fgets(buf,sizeof(buf),fp); switch(buf[0]) { case 'm': /* move node */ if (sscanf(buf+1,"%s %lf%lf",name,&x,&y) == 3) { np = getnode(G,name); if (np) { NP_pos(np)[0] = x; NP_pos(np)[1] = y; diffeq_model(); } } break; case 'q': return FALSE; default: agerr(AGERR, "unknown command '%s', ignored\n",buf); } return TRUE; } #endif return FALSE; } void final_energy(graph_t *G, int nG) { int i,j,d; double e = 0.0,t0,t1; node_t *ip,*jp; if (Verbose) { for (i = 0; i < nG - 1; i++) { ip = GD_neato_nlist(G)[i]; for (j = i+1; j < nG; j++) { jp = GD_neato_nlist(G)[j]; for (t0 = 0.0, d = 0; d < Ndim; d++) { t1 = (ND_pos(ip)[d] - ND_pos(jp)[d]); t0 = t1 * t1; } e = e + .5 * GD_spring(G)[i][j] * (t0 + GD_dist(G)[i][j] * GD_dist(G)[i][j] - 2.0 * GD_dist(G)[i][j] * sqrt(t0)); } } fprintf(stderr,"iterations = %d final e = %f\n",GD_move(G),e); } }