chiark / gitweb /
new arrangements for trivia etc., wip for movfeat stuff
[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_NODE_EDGEENDS(side,edgeend, node) {
408     segment= edgeend->edge->subseg->segment;
409     if (!all_segment) {
410       all_segment= segment;
411     } else if (all_segment != segment) {
412       trace(" not inner: %s and %s\n",
413             all_segment->segname, segment->segname);
414       return 0;
415     }
416   }
417   if (!all_segment) {
418     trace(" not inner: no edges?!\n");
419     return 0;
420   }
421   trace(" inner %s ",segment->segname);
422   return 1;
423 }
424
425 static EdgeEnd *movfeat_findmate(EdgeEnd *key) {
426   Node *node;
427   EdgeEnd *mate;
428   int mateside;
429
430   node= key->node->node;
431   mateside= !key->node->side;
432   
433   FOR_SIDE_EDGEENDS(mate, node,mateside) {
434     if (key->edge->subseg == mate->edge->subseg &&
435         key->edge->movpos == mate->edge->movpos &&
436         key->end != mate->end)
437       return mate;
438   }
439   return 0; /* no mate - must be a stub */
440 }
441
442 static void movfeatrmstubs(void) {
443   int pass, anychange, side;
444   Node *node;
445   Edge *edge;
446   EdgeEnd *edgeend, *mate;
447
448   pass= 0;
449   do {
450     anychange= 0;
451     pass++;
452     trace("movfeatrmstubs pass %d ...\n", pass);
453     FOR_ALL_NODES(node) {
454       trace("movfeatrmstubs ");
455       if (!movfeat_isinnernode(node)) continue;
456       FOR_NODE_EDGEENDS(side,edgeend, node) {
457         edge= edgeend->edge;
458         mate= movfeat_findmate(edgeend);
459         if (mate) {
460           trace("("); trace_edgeend(edgeend); trace(")");
461           continue;
462         }
463         edge_delete(edgeend);
464         anychange++;
465       }
466       trace("\n");
467     }
468     trace("movfeatrmstubs pass %d %s\n", pass, anychange?"changed":"alldone");
469   } while (anychange);
470 }
471
472 static void movfeatsplitnodes(void) {
473   Node *node, *newnode;
474   EdgeEnd *edgeend, *mate;
475   Edge *edge;
476   int side;
477   
478   FOR_ALL_NODES(node) {
479     trace("movfeatsplitnodes ");
480     if (!movfeat_isinnernode(node)) continue;
481     if (!node->sides[0].head->next) {
482       trace("pair-trivial\n");
483       continue;
484     }
485     FOR_NODE_EDGEENDS(side,edgeend, node) {
486       edge= edgeend->edge;
487       mate= movfeat_findmate(edgeend);
488       assert(mate);
489       newnode= node_create(masprintf("%s/*%d",node->pname,edge->movpos));
490       edgeend_replumb(mate, &newnode->sides[mate->node->side]);
491       edgeend_replumb(edgeend, &newnode->sides[edgeend->node->side]);
492     }
493   }
494 }
495
496 static void trivpairnodes(void) {
497   /* Eliminate near-trivial nodes - in this case, ones which have only
498    * two edges, which come in on opposite sides, have the same
499    * subsegspec, and with the two ends identically aligned.
500    */
501   EdgeEnd *leftedge, *rightedge;
502   Node *node;
503
504   FOR_ALL_NODES(node) {
505     trace("trivpairnodes node "); trace_node(node); trace(" ");
506     if (!(count_edges(&node->sides[0])==1 &&
507           count_edges(&node->sides[1])==1)) {
508       trace("no, a non-unitary side\n");
509       continue;
510     }
511     leftedge= node->sides[0].head;
512     rightedge= node->sides[1].head;
513     if (leftedge->edge->subseg != rightedge->edge->subseg) {
514       trace("no, (sub)segments differ\n");
515       continue;
516     }
517     if (leftedge->edge->movpos != rightedge->edge->movpos) {
518       trace("no, movpos's differ\n");
519       continue;
520     }
521     if (leftedge->end == rightedge->end) {
522       trace("no, opposite alignment\n");
523       continue;
524     }
525     trace(" yes:\n");
526     rightedge->edge->pname=
527       masprintf("%s+%s", leftedge->edge->pname, rightedge->edge->pname);
528     rightedge->edge->distance += leftedge->edge->distance;
529     edgeend_replumb(rightedge, edgeend_otherend(leftedge)->node);
530     edge_delete(leftedge);
531     trace(" trivpairnodes operation complete\n");
532   }
533 }
534
535 static void trivnullnodes(void) {
536   /* Eliminate trivial nodes - ones which have no edges. */
537   Node *node;
538   int side;
539   
540   trace("trivnullnodes ");
541   FOR_ALL_NODES(node) {
542     FOR_BOTH(side) {
543       if (node->sides[side].head)
544         goto non_null;
545     }
546     node_surely_orphaned(node);
547   non_null:;
548   }
549   trace(" trivnullnodes done.\n");
550 }
551
552 static void check(const char *why) {
553   Node *node;
554   int alloc_edone, used_edone;
555   typedef struct { Edge *edge; int endsdone; } EdgeDone;
556   EdgeDone *edone, *search_edone;
557   int side;
558   EdgeEnd *edgeend;
559   Edge *edge;
560
561   edone= 0;
562   alloc_edone= used_edone= 0;
563   
564   trace("consistency check (%s) ...\n",why);
565   FOR_ALL_NODES(node) {
566     trace(" consistency "); trace_node(node); trace("\n");
567     LIST_CHECKNODE(all_nodes,node);
568     FOR_BOTH(side) {
569       trace(" consistency "); trace_nodeside(&node->sides[side]); trace("\n");
570       assert(node->sides[side].node == node);
571       assert(node->sides[side].side == side);
572       FOR_SIDE_EDGEENDS(edgeend, node,side) {
573         trace(" consistency "); trace_nodeside(&node->sides[side]);
574         trace(" "); trace_edgeend(edgeend); trace("\n");
575         
576         edge= edgeend->edge;
577         LIST_CHECKNODE(node->sides[side], edgeend);
578         assert(edgeend == &edge->ends[edgeend->end]);
579         assert(edgeend->node == &node->sides[side]);
580         if (used_edone==alloc_edone) {
581           alloc_edone += 2; alloc_edone *= 2;
582           edone= MREALLOC(edone, alloc_edone * sizeof(*edone));
583         }
584         for (search_edone= edone;
585              search_edone < edone + used_edone && search_edone->edge != edge;
586              search_edone++);
587         if (search_edone >= edone + used_edone) {
588           search_edone->edge= edge;
589           search_edone->endsdone= 0;
590           used_edone++;
591         }
592         search_edone->endsdone++;
593         assert(search_edone->endsdone <= 2);
594       }
595     }
596   }
597   for (search_edone= edone;
598        search_edone < edone + used_edone;
599        search_edone++) {
600     trace("consistency "); trace_edge(search_edone->edge);
601     trace(" count=%d\n", search_edone->endsdone);
602     assert(search_edone->endsdone == 2);
603   }
604   trace("consistency check (%s) ok\n",why);
605 }
606
607 static void consistency(void) {
608   check("command-line request");
609 }
610
611 static void printforneato(void) {
612   Node *node;
613   EdgeEnd *edgeend;
614   Edge *edge;
615   int side;
616   
617   output("digraph L {\n");
618
619   FOR_ALL_NODES(node) {
620     output("  n%sO [label=\"%s\"];\n"
621            "  n%sI [label=\"+\"];\n",
622            node->pname, node->pname, node->pname);
623     output("  n%sO -> n%sI [len=0.25 arrowsize=0];\n",
624            node->pname, node->pname);
625     FOR_EDGES(edge, node,side,edgeend) {
626       output("  n%s%c -> n%s%c [label=\"%s:",
627              edge->ends[0].node->node->pname,
628              "OI"[edge->ends[0].node->side],
629              edge->ends[1].node->node->pname,
630              "OI"[edge->ends[1].node->side],
631              edge->pname);
632       if (!edge->subseg->segment->segname) {
633         output("?");
634       } else {
635         output(edge->subseg->segment->segname);
636         if (edge->subseg->movfeat) {
637           output("/%s%d",edge->subseg->movfeat,edge->movpos);
638         }
639       }
640       output(":%.2f\"];\n",edge->distance);
641     }
642   }
643   output("}\n");
644 }
645
646 typedef struct {
647   const char *name;
648   void (*fn)(void);
649 } OpInfo;
650
651 static const OpInfo opinfos[]= {
652 #define OI(x) { #x, x },
653   OI(printforneato)
654   OI(consistency)
655   OI(movfeatsplitedges)
656   OI(movfeatrmstubs)
657   OI(movfeatsplitnodes)
658   OI(trivpairnodes)
659   OI(trivnullnodes)
660   { 0,0 }
661 };
662
663 static void usage(void) {
664   const OpInfo *oi;
665   
666   fprintf(stderr,
667           "usage: .../redactgraph <operation> [<operation>...]\n"
668           "operations:\n");
669   for (oi=opinfos; oi->name; oi++) {
670     fprintf(stderr," %s\n",oi->name);
671   }
672   fflush(stderr);
673   exit(8);
674 }
675
676 int main(int argc, const char *const *argv) {
677   const OpInfo *oi;
678   const char *arg;
679
680   if (!argv[0] || !argv[1]) usage();
681
682   while ((arg= *++argv)) {
683     for (oi=opinfos; oi->name && strcmp(arg,oi->name); oi++);
684     if (!oi->name) usage();
685     oi->fn();
686   }
687   trace("\n");
688   output("\n");
689   if (ferror(stdout) || fflush(stdout)) { perror("stdout"); exit(12); }
690   exit(0);
691 }