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