chiark / gitweb /
shorter pnames from trivpairnodes
[trains.git] / layout / redactgraph.c
1 /*
2  */
3 /* for debugging, runes like
4  *  ./ours.redactgraph consistency movfeatsplitedges consistency movfeatrmstubs consistency movfeatsplitnodes consistency trivpairnodes consistency trivnullnodes consistency printforneato | neato -Tps >u.ps
5  * are often useful.
6  */
7
8 #include <stdarg.h>
9 #include <string.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <assert.h>
13 #include <errno.h>
14
15 #include "dlist.h"
16 #include "graph-data.h"
17
18 #define MMALLOC(sz) mmalloc(sz,__FILE__,__LINE__)
19 #define MREALLOC(p,sz) mrealloc(p,sz,__FILE__,__LINE__)
20
21 static void *mmalloccheck(void *p, const char *op, size_t sz,
22                           const char *file, int line) {
23   if (p) return p;
24   fprintf(stderr,"%s:%d: %s of %lu failed: %s\n",
25           file, line, op, (unsigned long)sz, strerror(errno));
26   exit(16);
27 }
28
29 static void *mmalloc(size_t sz, const char *file, int line) {
30   void *p;
31   if (!sz) return 0;
32   p= malloc(sz);
33   return mmalloccheck(p,"malloc",sz,file,line);
34 }
35
36 static void *mrealloc(void *p, size_t sz, const char *file, int line) {
37   p= realloc(p,sz);
38   return mmalloccheck(p,"realloc",sz,file,line);
39 }  
40
41 static char *mvasprintf(const char *fmt, va_list al)
42      __attribute__((format(printf,1,0)));
43 static char *mvasprintf(const char *fmt, va_list al) {
44   char *p;
45   int r;
46   
47   r= vasprintf(&p,fmt,al);
48   if (r<0) {
49     fprintf(stderr,"vasprintf(\"%s\",...) failed: %s\n",
50             fmt, strerror(errno));
51     exit(16);
52   }
53   return p;
54 }
55
56 static char *masprintf(const char *fmt, ...)
57      __attribute__((format(printf,1,2)));
58 static char *masprintf(const char *fmt, ...) {
59   va_list al;
60   char *p;
61   va_start(al,fmt);
62   p= mvasprintf(fmt,al);
63   va_end(al);
64   return p;
65 }
66
67 /*---------- output processing ----------*/
68
69 static void voutputstuff(int trace, const char *fmt, va_list al)
70      __attribute__((format(printf,2,0)));
71 static void voutputstuff(int trace, const char *fmt, va_list al) {
72   static struct OutputBuf {
73     int used, allocd;
74     char *buf;
75   } bufs[2];
76
77   struct OutputBuf *b;
78   char *s, *p, *newline;
79   int l;
80
81   b= &bufs[trace];
82   s= mvasprintf(fmt,al);
83   p= s;
84
85   for (;;) {
86     newline= strchr(p,'\n');
87     if (newline) { *newline= 0; }
88     l= strlen(p);
89     if (l+b->used > b->allocd) {
90       b->allocd= l+b->used;
91       b->buf= MREALLOC(b->buf, b->allocd);
92     }
93     memcpy(b->buf+b->used, p, l);
94     b->used += l;
95     
96     if (!newline) break;
97
98     if (trace) { fputs("# ",stdout); }
99     fwrite(b->buf, 1,b->used, stdout);
100     putc('\n',stdout);
101     if (ferror(stdout) || fflush(stdout)) {
102       perror("stdout voutputstuff"); exit(12);
103     }
104     b->used= 0;
105     p= newline+1;
106   }
107
108   free(s);
109 }      
110
111 #define OF(fn,tr)                                                           \
112   static void v##fn(const char *fmt, va_list al)                            \
113      __attribute__((format(printf,1,0)));                                   \
114   static void v##fn(const char *fmt, va_list al) {                          \
115     voutputstuff(tr,fmt,al);                                                \
116   }                                                                         \
117   static void fn(const char *fmt, ...) __attribute__((format(printf,1,2))); \
118   static void fn(const char *fmt, ...) {                                    \
119     va_list al;                                                             \
120     va_start(al,fmt);                                                       \
121     v##fn(fmt,al);                                                          \
122     va_end(al);                                                             \
123   }
124 OF(output,0)
125 OF(trace,1)
126      
127 /*---------- traceing of various things ----------*/
128
129 static void trace_node(Node *node) {
130   trace("n%s",node->pname);
131 }
132
133 static void trace_nodeside(NodeSide *node) {
134   trace("n%s %c",node->node->pname,"OI"[node->side]);
135 }
136
137 static void trace_edge(Edge *edge) {
138   trace("e%s",edge->pname);
139 }
140
141 static void trace_edgeend(EdgeEnd *edge) {
142   trace("e%s %c",edge->edge->pname,"OI"[edge->end]);
143 }
144
145 static void trace_combpos(int combpos, Segment *seg) {
146   int i, weight;
147   MovFeat *f;
148   
149   trace("*");
150   weight= 1;
151   FOR_SEGMENT_MOVFEATS(i,f,seg) {
152     if (!f->movfeat) continue;
153     trace("%s%d", f->movfeat, (combpos / weight) % f->n_positions);
154     weight *= f->n_positions;
155   }
156 }
157
158 /*---------- general graph-manipulation tools ----------*/
159
160 static EdgeEnd *edgeend_otherend(EdgeEnd *thisend) {
161   return &thisend->edge->ends[!thisend->end];
162 }
163
164 static int count_edges(const NodeSide *node) {
165   int count;
166   const EdgeEnd *edge;
167   
168   for (count=0, edge=node->head;
169        edge;
170        count++, edge=edge->next);
171   return count;
172 }
173
174 static Edge *edge_create(const char *pname, double distance,
175                          MovFeat *subseg, int movpos) {
176   Edge *e;
177   int end;
178   
179   e= MMALLOC(sizeof(*e));
180   e->pname= pname;
181   e->distance= distance;
182   e->subseg= subseg;
183   e->movpos= movpos;
184   FOR_BOTH(end) {
185     e->ends[end].edge= e;
186     e->ends[end].end= end;
187   }
188   trace("[new:"); trace_edge(e); trace("]");
189   return e;
190 }
191
192 static Node *node_create(const char *pname) {
193   Node *n;
194   int side;
195
196   n= MMALLOC(sizeof(*n));
197   n->pname= pname;
198   FOR_BOTH(side) {
199     n->sides[side].node= n;
200     n->sides[side].side= side;
201     LIST_INIT(n->sides[side]);
202   }
203   LIST_LINK_TAIL(all_nodes,n);
204   return n;
205 }
206
207 /* Perhaps surprisingly, it's OK to remove nodes and edges in loops
208  * made with the macros in graph-data.h, provided that if the
209  * _current_ node or edge is removed, that happens last.  This is
210  * because removing a node or edge it doesn't actually free it or
211  * change its contents, so the thing->next is still valid afterwards.
212  * This depends on the fact that we never free anything we're done
213  * with !
214  */
215
216 static void edgeend_detach(EdgeEnd *toremove) {
217   trace("[detach:"); trace_edgeend(toremove);
218   trace("-"); trace_nodeside(toremove->node); trace("]");
219   LIST_UNLINK(*toremove->node, toremove);
220 }
221 static void edgeend_attach(EdgeEnd *toattach, NodeSide *newtarget) {
222   trace("[attach:"); trace_edgeend(toattach);
223   trace("-"); trace_nodeside(newtarget); trace("]");
224   LIST_LINK_TAIL(*newtarget, toattach);
225   toattach->node= newtarget;
226 }
227 static void edgeend_replumb(EdgeEnd *tomove, NodeSide *newtarget) {
228   edgeend_detach(tomove);
229   edgeend_attach(tomove,newtarget);
230 }
231
232 static void edge_delete(EdgeEnd *detachlast) {
233   Edge *edge;
234   edge= detachlast->edge;
235   edgeend_detach(&edge->ends[!detachlast->end]);
236   edgeend_detach(detachlast);
237   trace("[delete:"); trace_edge(edge); trace("]");
238 }
239
240 static void node_surely_orphaned(Node *node) {
241   trace("[nodeorphan:"); trace_node(node); trace("]");
242   assert(!node->sides[0].head);
243   assert(!node->sides[1].head);
244   LIST_UNLINK(all_nodes, node);
245 }
246
247 /*---------- operations ----------*/
248
249   /* We combine the individual moveable features in each segment into
250    * a single `feature' with the cross-product of all of the
251    * positions.  The new feature does not appear in
252    * Segment->[n_]movfeats, but Edge->subseg and Edge->movpos are
253    * updated (ie Edge->subseg->segment->movfeats is the original array
254    * containing only the actual features, and so does not contain
255    * Edge->subseg).  It is often printed as if its name was `*' (which
256    * is not legal in input files etc.).  The positions of the
257    * *-feature are numbered according to the obvious arithmetic coding
258    * of the individual actual feature positions.  See also
259    * hostside/safety.h.
260    *
261    * The conversion has two stages:
262    *
263    * FIRST STAGE (movfeatsplitedges):
264    *
265    * We multiply up each edge of any segment with moveable features to
266    * be an edge which is a position of the *-feature.
267    *
268    * For example, if segment A1 has feature P with states 0 and 1
269    * and feature J with states 0 and 1, then the edges are translated
270    * as follows
271    *   old edge    new edges
272    *    A1          A1/P0J0 A1/P0J1 A1/P1J0 A1/P1J1
273    *    A1/P0       A1/P0J0 A1/P0J1
274    *    A1/P1                       A1/P1J0 A1/P1J1
275    *    A1/J0       A1/P0J0         A1/P1J0        
276    *    A1/J1               A1/P0J1         A1/P1J1
277    * Eg,
278    *     *----A1/P0----*   becomes   *----A1/P0J0----*
279    *                                  `---A1/P0J1---'
280    *
281    * SECOND STAGE (movfeatrmstubs):
282    *
283    * We remove stub moveable edges: whenever, at a node which has only
284    * edges which are part of the same segment, we find an edge on one
285    * side in which is not matched exactly (moveable feature settings
286    * and direction) by an edge on the other side, we delete that extra
287    * edge:
288    *
289    * is split into one node
290    * for each position and direction (directions are
291    * end0-to-side0+end1-to-side1 and end0-to-side1+end1-to-side0), to
292    * which the corresponding edges are connected.  Any resulting node
293    * which has only one edge is removed together with the edge.
294    *
295    * For example (neglecting direction for the purposes of the
296    * example), when we start with:
297    *
298    *             ,---A1/P0J0---.                  
299    *         ...*----A1/P0J1----*---A1/P0J1---*...
300    *             `---A1/P1J0---' `--A1/P1J1--'    
301    *              `--A1/P1J1--'                   
302    *
303    * the edges A1/P0J0 and A1/P1J1 are not matched on the RHS of the node,
304    * so they are removed:
305    *
306    *         ...*----A1/P0J1----*---A1/P0J1---*...
307    *             `---A1/P1J0---' `--A1/P1J1--'    
308    *
309    *
310    * THIRD STAGE:
311    *
312    * We split nodes: every node which has only edges which are part of
313    * the same segment is split into one node for each kind of edge
314    * which goes through it.  The 2nd stage ensures that this is always
315    * straightforward.
316    *
317    * Our example above turns into:
318    *
319    *         ...*----A1/P0J1----*---A1/P0J1---*...
320    *             `---A1/P1J0----*---A1/P1J1--'
321    */
322
323 static void movfeatsplitedges(void) {
324   Node *node;
325   int side, l, i, weight, combpos, end;
326   EdgeEnd *edgeend;
327   Edge *edge, *newedge;
328   Segment *segment;
329   MovFeat *subseg, *starfeature, *loopfeat;
330   char *featstr;
331   
332   trace("movfeatsplitedges ...\n");
333   FOR_ALL_NODES(node) {
334     FOR_EDGES(edge, node,side,edgeend) {
335       subseg= edge->subseg;
336       segment= subseg->segment;
337
338       trace("movfeatsplitedges "); trace_edge(edge);
339       trace(" segment %s ", segment->segname);
340       if (segment->n_movfeats<=1) { trace("no moveables\n"); continue; }
341       if (subseg == segment->starfeature) { trace("*-feature\n"); continue; }
342
343       starfeature= segment->starfeature;
344       if (starfeature) {
345         trace("old");
346       } else {
347         trace("new");
348         starfeature= MMALLOC(sizeof(*starfeature));
349         starfeature->segment= segment;
350
351         l=2;  starfeature->n_positions=1;
352         FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
353           if (!loopfeat->movfeat) continue;
354           l += strlen(loopfeat->movfeat);
355           starfeature->n_positions *= loopfeat->n_positions;
356         }
357
358         starfeature->movfeat= featstr= MMALLOC(l);
359         strcpy(featstr, "*");
360
361         FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
362           if (!loopfeat->movfeat) continue;
363           strcat(featstr, loopfeat->movfeat);
364         }
365
366         segment->starfeature= starfeature;
367       }
368       trace(" %s(%d) ", starfeature->movfeat, starfeature->n_positions);
369
370       if (subseg->movfeat) {
371         weight= 1;
372         FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
373           if (loopfeat == subseg) goto found;
374           weight *= loopfeat->n_positions;
375         }
376         abort();
377       found:
378         trace("%s(x%d)",subseg->movfeat,weight);
379       } else {
380         weight= 1;
381         trace("fixed");
382       }
383
384       trace(" ...\n");
385         
386       for (combpos=0; combpos<starfeature->n_positions; combpos++) {
387         trace("movfeatsplitedges  "); trace_combpos(combpos,segment);
388         if ((combpos / weight) % subseg->n_positions != edge->movpos) {
389           trace(" excluded\n");
390           continue;
391         }
392         newedge= edge_create(masprintf("%s/*%d",edge->pname,combpos),
393                              edge->distance,
394                              starfeature,
395                              combpos);
396         FOR_BOTH(end)
397           edgeend_attach(&newedge->ends[end], edge->ends[end].node);
398       }
399       edge_delete(edgeend);
400     }
401   }
402   trace("movfeatsplitedges complete.\n");
403 }
404
405 static int movfeat_isinnernode(Node *node) {
406   int side;
407   EdgeEnd *edgeend;
408   Segment *all_segment, *segment;
409
410   trace_node(node);
411   all_segment= 0;
412   FOR_BOTH(side) {
413     if (!node->sides[side].head) {
414       trace(" %c terminus\n", "OI"[side]);
415       return 0;
416     }
417   }
418   FOR_NODE_EDGEENDS(side,edgeend, node) {
419     segment= edgeend->edge->subseg->segment;
420     if (!all_segment) {
421       all_segment= segment;
422     } else if (all_segment != segment) {
423       trace(" not inner: %s and %s\n",
424             all_segment->segname, segment->segname);
425       return 0;
426     }
427   }
428   if (!all_segment) {
429     trace(" not inner: no edges?!\n");
430     return 0;
431   }
432   trace(" inner %s ",segment->segname);
433   return 1;
434 }
435
436 static EdgeEnd *movfeat_findmate(EdgeEnd *key) {
437   Node *node;
438   EdgeEnd *mate;
439   int mateside;
440
441   node= key->node->node;
442   mateside= !key->node->side;
443   
444   FOR_SIDE_EDGEENDS(mate, node,mateside) {
445     if (key->edge->subseg == mate->edge->subseg &&
446         key->edge->movpos == mate->edge->movpos &&
447         key->end != mate->end)
448       return mate;
449   }
450   return 0; /* no mate - must be a stub */
451 }
452
453 static void movfeatrmstubs(void) {
454   int pass, anychange, side;
455   Node *node;
456   Edge *edge;
457   EdgeEnd *edgeend, *mate;
458
459   pass= 0;
460   do {
461     anychange= 0;
462     pass++;
463     trace("movfeatrmstubs pass %d ...\n", pass);
464     FOR_ALL_NODES(node) {
465       trace("movfeatrmstubs ");
466       if (!movfeat_isinnernode(node)) continue;
467       FOR_NODE_EDGEENDS(side,edgeend, node) {
468         edge= edgeend->edge;
469         mate= movfeat_findmate(edgeend);
470         if (mate) {
471           trace("("); trace_edgeend(edgeend); trace(")");
472           continue;
473         }
474         edge_delete(edgeend);
475         anychange++;
476       }
477       trace("\n");
478     }
479     trace("movfeatrmstubs pass %d %s\n", pass, anychange?"changed":"alldone");
480   } while (anychange);
481 }
482
483 static void movfeatsplitnodes(void) {
484   Node *node, *newnode;
485   EdgeEnd *edgeend, *mate;
486   Edge *edge;
487   int side;
488   
489   FOR_ALL_NODES(node) {
490     trace("movfeatsplitnodes ");
491     if (!movfeat_isinnernode(node)) continue;
492     if (!node->sides[0].head->next) {
493       trace("pair-trivial\n");
494       continue;
495     }
496     FOR_NODE_EDGEENDS(side,edgeend, node) {
497       edge= edgeend->edge;
498       mate= movfeat_findmate(edgeend);
499       assert(mate);
500       newnode= node_create(masprintf("%sx%d",node->pname,edge->movpos));
501       edgeend_replumb(mate, &newnode->sides[mate->node->side]);
502       edgeend_replumb(edgeend, &newnode->sides[edgeend->node->side]);
503     }
504   }
505 }
506
507 static void trivpairnodes(void) {
508   /* Eliminate near-trivial nodes - in this case, ones which have only
509    * two edges, which come in on opposite sides, have the same
510    * subsegspec, and with the two ends identically aligned.
511    */
512   EdgeEnd *leftedge, *rightedge;
513   Node *node;
514
515   FOR_ALL_NODES(node) {
516     trace("trivpairnodes node "); trace_node(node); trace(" ");
517     if (!(count_edges(&node->sides[0])==1 &&
518           count_edges(&node->sides[1])==1)) {
519       trace("no, a non-unitary side\n");
520       continue;
521     }
522     leftedge= node->sides[0].head;
523     rightedge= node->sides[1].head;
524     if (leftedge->edge->subseg != rightedge->edge->subseg) {
525       trace("no, (sub)segments differ\n");
526       continue;
527     }
528     if (leftedge->edge->movpos != rightedge->edge->movpos) {
529       trace("no, movpos's differ\n");
530       continue;
531     }
532     if (leftedge->end == rightedge->end) {
533       trace("no, opposite alignment\n");
534       continue;
535     }
536     trace(" yes:\n");
537     if (!strchr(rightedge->edge->pname, '+')) {
538       rightedge->edge->pname=
539         masprintf("%s+", rightedge->edge->pname);
540     }
541     rightedge->edge->distance += leftedge->edge->distance;
542     edgeend_replumb(rightedge, edgeend_otherend(leftedge)->node);
543     edge_delete(leftedge);
544     trace(" trivpairnodes operation complete\n");
545   }
546 }
547
548 static void trivnullnodes(void) {
549   /* Eliminate trivial nodes - ones which have no edges. */
550   Node *node;
551   int side;
552   
553   trace("trivnullnodes ");
554   FOR_ALL_NODES(node) {
555     FOR_BOTH(side) {
556       if (node->sides[side].head)
557         goto non_null;
558     }
559     node_surely_orphaned(node);
560   non_null:;
561   }
562   trace(" trivnullnodes done.\n");
563 }
564
565 static void check(const char *why) {
566   Node *node;
567   int alloc_edone, used_edone;
568   typedef struct { Edge *edge; int endsdone; } EdgeDone;
569   EdgeDone *edone, *search_edone;
570   int side;
571   EdgeEnd *edgeend;
572   Edge *edge;
573
574   edone= 0;
575   alloc_edone= used_edone= 0;
576   
577   trace("consistency check (%s) ...\n",why);
578   FOR_ALL_NODES(node) {
579     trace(" consistency "); trace_node(node); trace("\n");
580     LIST_CHECKNODE(all_nodes,node);
581     FOR_BOTH(side) {
582       trace(" consistency "); trace_nodeside(&node->sides[side]); trace("\n");
583       assert(node->sides[side].node == node);
584       assert(node->sides[side].side == side);
585       FOR_SIDE_EDGEENDS(edgeend, node,side) {
586         trace(" consistency "); trace_nodeside(&node->sides[side]);
587         trace(" "); trace_edgeend(edgeend); trace("\n");
588         
589         edge= edgeend->edge;
590         LIST_CHECKNODE(node->sides[side], edgeend);
591         assert(edgeend == &edge->ends[edgeend->end]);
592         assert(edgeend->node == &node->sides[side]);
593         if (used_edone==alloc_edone) {
594           alloc_edone += 2; alloc_edone *= 2;
595           edone= MREALLOC(edone, alloc_edone * sizeof(*edone));
596         }
597         for (search_edone= edone;
598              search_edone < edone + used_edone && search_edone->edge != edge;
599              search_edone++);
600         if (search_edone >= edone + used_edone) {
601           search_edone->edge= edge;
602           search_edone->endsdone= 0;
603           used_edone++;
604         }
605         search_edone->endsdone++;
606         assert(search_edone->endsdone <= 2);
607       }
608     }
609   }
610   for (search_edone= edone;
611        search_edone < edone + used_edone;
612        search_edone++) {
613     trace("consistency "); trace_edge(search_edone->edge);
614     trace(" count=%d\n", search_edone->endsdone);
615     assert(search_edone->endsdone == 2);
616   }
617   trace("consistency check (%s) ok\n",why);
618 }
619
620 static void consistency(void) {
621   check("command-line request");
622 }
623
624 static void printforneato(void) {
625   Node *node;
626   EdgeEnd *edgeend;
627   Edge *edge;
628   int side;
629   
630   output("digraph L {\n");
631
632   FOR_ALL_NODES(node) {
633     output("  n%sO [label=\"%s\"];\n"
634            "  n%sI [label=\"+\"];\n",
635            node->pname, node->pname, node->pname);
636     output("  n%sO -> n%sI [len=0.25 arrowsize=0];\n",
637            node->pname, node->pname);
638     FOR_EDGES(edge, node,side,edgeend) {
639       output("  n%s%c -> n%s%c [label=\"%s:",
640              edge->ends[0].node->node->pname,
641              "OI"[edge->ends[0].node->side],
642              edge->ends[1].node->node->pname,
643              "OI"[edge->ends[1].node->side],
644              edge->pname);
645       if (!edge->subseg->segment->segname) {
646         output("?");
647       } else {
648         output(edge->subseg->segment->segname);
649         if (edge->subseg->movfeat) {
650           output("/%s%d",edge->subseg->movfeat,edge->movpos);
651         }
652       }
653       output(":%.2f\"];\n",edge->distance);
654     }
655   }
656   output("}\n");
657 }
658
659 typedef struct {
660   const char *name;
661   void (*fn)(void);
662 } OpInfo;
663
664 static const OpInfo opinfos[]= {
665 #define OI(x) { #x, x },
666   OI(printforneato)
667   OI(consistency)
668   OI(movfeatsplitedges)
669   OI(movfeatrmstubs)
670   OI(movfeatsplitnodes)
671   OI(trivpairnodes)
672   OI(trivnullnodes)
673   { 0,0 }
674 };
675
676 static void usage(void) {
677   const OpInfo *oi;
678   
679   fprintf(stderr,
680           "usage: .../redactgraph <operation> [<operation>...]\n"
681           "operations:\n");
682   for (oi=opinfos; oi->name; oi++) {
683     fprintf(stderr," %s\n",oi->name);
684   }
685   fflush(stderr);
686   exit(8);
687 }
688
689 int main(int argc, const char *const *argv) {
690   const OpInfo *oi;
691   const char *arg;
692
693   if (!argv[0] || !argv[1]) usage();
694
695   while ((arg= *++argv)) {
696     for (oi=opinfos; oi->name && strcmp(arg,oi->name); oi++);
697     if (!oi->name) usage();
698     oi->fn();
699   }
700   trace("\n");
701   output("\n");
702   if (ferror(stdout) || fflush(stdout)) { perror("stdout"); exit(12); }
703   exit(0);
704 }