chiark / gitweb /
segment labelling work-in-progress - yet to do are labels and graph colouring
[trains.git] / layout / redactgraph.c
index a7945bb1e8f3743514d093deb9359012ad05c0fd..09bc197dfc1fccff9f4e5fb33563304c3dd7250e 100644 (file)
@@ -1,16 +1,99 @@
-/**/
+/*
+ * output format from printforforsafety is lines like this:
+ *   segment <node>.<side> <node>.<side> <segment>[/<movinfo>] <distance>
+ *   #...         (comment)
+ *                (blank)
+ *   end          (at end of file, mandatory)
+ * syntactic restrictions:
+ *   <node> is alphanumeric and starts with a letter
+ *   <side> is precisely the digit 0 or the digit 1
+ *   <segment> is alphanumeric (and comes from the layout)
+ *   <movinfo> is  <movfeat><movpos>[<movfeat><movpos>...]*<combpos>
+ *            where <movfeat> is alphabetic and <movpos> is and
+ *            <combpos> are in decimal and have no unnecessary leading 0's
+ *   <distance> contains only digits and perhaps decimal point
+ * semantic restrictions (not enforced by redactgraph but ought to
+ * be impossible for layout designer to violate):
+ *   Each <segment>[/<movinfo>] appears exactly once.
+ *   <segment> appears either once without /<movinfo> or always with.
+ *   Each side of each node appears either
+ *    - once as the 0th or 1th end of a <segment>
+ *    - one or more times as the same end of <segment>/<movinfo>'s with
+ *      the same <segment>
+ *    - not at all (indicates a terminus)
+ * Refer to safety.h for info regarding <combpos>.
+ *
+ * See layout-data.h comment for info regarding layout data for
+ * control system.
+ */
+/* for debugging, runes like
+ *  ./ours.redactgraph consistency movfeatsplitedges consistency movfeatrmstubs consistency movfeatsplitnodes consistency trivpairnodes consistency trivnullnodes consistency printforneato | neato -Tps >u.ps
+ * are often useful.
+ */
 
 #include <stdarg.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <assert.h>
+#include <errno.h>
 
 #include "dlist.h"
 #include "graph-data.h"
 
+#define MMALLOC(sz) mmalloc(sz,__FILE__,__LINE__)
+#define MREALLOC(p,sz) mrealloc(p,sz,__FILE__,__LINE__)
+
+static void *mmalloccheck(void *p, const char *op, size_t sz,
+                         const char *file, int line) {
+  if (p) return p;
+  fprintf(stderr,"%s:%d: %s of %lu failed: %s\n",
+         file, line, op, (unsigned long)sz, strerror(errno));
+  exit(16);
+}
+
+static void *mmalloc(size_t sz, const char *file, int line) {
+  void *p;
+  if (!sz) return 0;
+  p= malloc(sz);
+  return mmalloccheck(p,"malloc",sz,file,line);
+}
+
+static void *mrealloc(void *p, size_t sz, const char *file, int line) {
+  p= realloc(p,sz);
+  return mmalloccheck(p,"realloc",sz,file,line);
+}  
+
+static char *mvasprintf(const char *fmt, va_list al)
+     __attribute__((format(printf,1,0)));
+static char *mvasprintf(const char *fmt, va_list al) {
+  char *p;
+  int r;
+  
+  r= vasprintf(&p,fmt,al);
+  if (r<0) {
+    fprintf(stderr,"vasprintf(\"%s\",...) failed: %s\n",
+           fmt, strerror(errno));
+    exit(16);
+  }
+  return p;
+}
+
+static char *masprintf(const char *fmt, ...)
+     __attribute__((format(printf,1,2)));
+static char *masprintf(const char *fmt, ...) {
+  va_list al;
+  char *p;
+  va_start(al,fmt);
+  p= mvasprintf(fmt,al);
+  va_end(al);
+  return p;
+}
+
 /*---------- output processing ----------*/
 
+static void voutputstuff(int trace, const char *fmt, va_list al)
+     __attribute__((format(printf,2,0)));
 static void voutputstuff(int trace, const char *fmt, va_list al) {
   static struct OutputBuf {
     int used, allocd;
@@ -19,10 +102,10 @@ static void voutputstuff(int trace, const char *fmt, va_list al) {
 
   struct OutputBuf *b;
   char *s, *p, *newline;
-  int r, l;
+  int l;
 
   b= &bufs[trace];
-  r= vasprintf(&s,fmt,al);  if (r<0) { perror("vasprintf"); exit(16); }
+  s= mvasprintf(fmt,al);
   p= s;
 
   for (;;) {
@@ -31,8 +114,7 @@ static void voutputstuff(int trace, const char *fmt, va_list al) {
     l= strlen(p);
     if (l+b->used > b->allocd) {
       b->allocd= l+b->used;
-      b->buf= realloc(b->buf, b->allocd);
-      if (!b->buf) { perror("realloc voutputstuff"); exit(16); }
+      b->buf= MREALLOC(b->buf, b->allocd);
     }
     memcpy(b->buf+b->used, p, l);
     b->used += l;
@@ -52,29 +134,55 @@ static void voutputstuff(int trace, const char *fmt, va_list al) {
   free(s);
 }      
 
-#define OF(fn,tr)                                      \
-  static void v##fn(const char *fmt, va_list al) {     \
-    voutputstuff(tr,fmt,al);                           \
-  }                                                    \
-  static void fn(const char *fmt, ...) {               \
-    va_list al;                                                \
-    va_start(al,fmt);                                  \
-    v##fn(fmt,al);                                     \
-    va_end(al);                                                \
+#define OF(fn,tr)                                                          \
+  static void v##fn(const char *fmt, va_list al)                           \
+     __attribute__((format(printf,1,0)));                                  \
+  static void v##fn(const char *fmt, va_list al) {                         \
+    voutputstuff(tr,fmt,al);                                               \
+  }                                                                        \
+  static void fn(const char *fmt, ...) __attribute__((format(printf,1,2))); \
+  static void fn(const char *fmt, ...) {                                   \
+    va_list al;                                                                    \
+    va_start(al,fmt);                                                      \
+    v##fn(fmt,al);                                                         \
+    va_end(al);                                                                    \
   }
 OF(output,0)
 OF(trace,1)
      
-/*---------- general graph-manipulation and printing stuff ----------*/
+/*---------- traceing of various things ----------*/
 
 static void trace_node(Node *node) {
-  trace("n%p",node);
+  trace("n%s",node->pname);
+}
+
+static void trace_nodeside(NodeSide *node) {
+  trace("n%s %c",node->node->pname,"OI"[node->side]);
+}
+
+static void trace_edge(Edge *edge) {
+  trace("e%s",edge->pname);
 }
 
 static void trace_edgeend(EdgeEnd *edge) {
-  trace("n%p%c",edge->node->node,"OI"[edge->node->side]);
+  trace("e%s %c",edge->edge->pname,"OI"[edge->end]);
 }
 
+static void trace_combpos(int combpos, Segment *seg) {
+  int i, weight;
+  MovFeat *f;
+  
+  trace("*");
+  weight= 1;
+  FOR_SEGMENT_MOVFEATS(i,f,seg) {
+    if (!f->movfeat) continue;
+    trace("%s%d", f->movfeat, (combpos / weight) % f->n_positions);
+    weight *= f->n_positions;
+  }
+}
+
+/*---------- general graph-manipulation tools ----------*/
+
 static EdgeEnd *edgeend_otherend(EdgeEnd *thisend) {
   return &thisend->edge->ends[!thisend->end];
 }
@@ -89,123 +197,358 @@ static int count_edges(const NodeSide *node) {
   return count;
 }
 
-static void edgeend_disconnect(EdgeEnd *toremove) {
-  LIST_UNLINK(*toremove->node, toremove);
+static Edge *edge_create(const char *pname, double distance,
+                        MovFeat *subseg, int movpos) {
+  Edge *e;
+  int end;
+  
+  e= MMALLOC(sizeof(*e));
+  e->pname= pname;
+  e->distance= distance;
+  e->subseg= subseg;
+  e->movpos= movpos;
+  FOR_BOTH(end) {
+    e->ends[end].edge= e;
+    e->ends[end].end= end;
+  }
+  trace("[new:"); trace_edge(e); trace("]");
+  return e;
+}
+
+static Node *node_create(const char *pname) {
+  Node *n;
+  int side;
+
+  n= MMALLOC(sizeof(*n));
+  n->pname= pname;
+  FOR_BOTH(side) {
+    n->sides[side].node= n;
+    n->sides[side].side= side;
+    LIST_INIT(n->sides[side]);
+  }
+  LIST_LINK_TAIL(all_nodes,n);
+  return n;
 }
 
+/* Perhaps surprisingly, it's OK to remove nodes and edges in loops
+ * made with the macros in graph-data.h, provided that if the
+ * _current_ node or edge is removed, that happens last.  This is
+ * because removing a node or edge it doesn't actually free it or
+ * change its contents, so the thing->next is still valid afterwards.
+ * This depends on the fact that we never free anything we're done
+ * with !
+ */
+
+static void edgeend_detach(EdgeEnd *toremove) {
+  trace("[detach:"); trace_edgeend(toremove);
+  trace("-"); trace_nodeside(toremove->node); trace("]");
+  LIST_UNLINK(*toremove->node, toremove);
+}
+static void edgeend_attach(EdgeEnd *toattach, NodeSide *newtarget) {
+  trace("[attach:"); trace_edgeend(toattach);
+  trace("-"); trace_nodeside(newtarget); trace("]");
+  LIST_LINK_TAIL(*newtarget, toattach);
+  toattach->node= newtarget;
+}
 static void edgeend_replumb(EdgeEnd *tomove, NodeSide *newtarget) {
-  edgeend_disconnect(tomove);
-  LIST_LINK_TAIL(*newtarget, tomove);
-  tomove->node= newtarget;
+  edgeend_detach(tomove);
+  edgeend_attach(tomove,newtarget);
 }
 
-static void edge_delete(Edge *edge) {
-  edgeend_disconnect(&edge->ends[0]);
-  edgeend_disconnect(&edge->ends[1]);
+static void edge_delete(EdgeEnd *detachlast) {
+  Edge *edge;
+  edge= detachlast->edge;
+  edgeend_detach(&edge->ends[!detachlast->end]);
+  edgeend_detach(detachlast);
+  trace("[delete:"); trace_edge(edge); trace("]");
 }
 
 static void node_surely_orphaned(Node *node) {
+  trace("[nodeorphan:"); trace_node(node); trace("]");
   assert(!node->sides[0].head);
   assert(!node->sides[1].head);
   LIST_UNLINK(all_nodes, node);
 }
 
-/*---------- operations ----------*/
-
-static void extendsplits(void) {
-  int pass, rightside, n;
-  Node *node;
-  EdgeEnd *leftedge, *rightedge;
-  
-  /*
-   * Whenever we have a node which has one or more moveable feature
-   * subsegments, part of the same moveable feature, on one side, and
-   * fixed portion of the same segment on the other, we eliminate
-   * the fixed portion and add its length to both the moveables,
-   * so that we have one subsegspec for the whole of each position:
+/*---------- operations - heavyweight graph redaction ----------*/
+
+  /* We combine the individual moveable features in each segment into
+   * a single `feature' with the cross-product of all of the
+   * positions.  The new feature does not appear in
+   * Segment->[n_]movfeats, but Edge->subseg and Edge->movpos are
+   * updated (ie Edge->subseg->segment->movfeats is the original array
+   * containing only the actual features, and so does not contain
+   * Edge->subseg).  It is often printed as if its name was `*' (which
+   * is not legal in input files etc.).  The positions of the
+   * *-feature are numbered according to the obvious arithmetic coding
+   * of the individual actual feature positions.  See also
+   * hostside/safety.h.
    *
-   *    <---l---> <----l'--->             <---------(l+l')------>
+   * The conversion has two stages:
    *
-   *   *----A1---*----A1/P0--*  becomes  *--------A1/P0----------*
-   *              `---A1/P1---*           `-------A1/P1-----------*
+   * FIRST STAGE (movfeatsplitedges):
    *
-   *              <----l''--->            <---------(l+l'')------>
+   * We multiply up each edge of any segment with moveable features to
+   * be an edge which is a position of the *-feature.
+   *
+   * For example, if segment A1 has feature P with states 0 and 1
+   * and feature J with states 0 and 1, then the edges are translated
+   * as follows
+   *   old edge    new edges
+   *    A1          A1/P0J0 A1/P0J1 A1/P1J0 A1/P1J1
+   *    A1/P0       A1/P0J0 A1/P0J1
+   *    A1/P1                       A1/P1J0 A1/P1J1
+   *    A1/J0       A1/P0J0         A1/P1J0        
+   *    A1/J1               A1/P0J1         A1/P1J1
+   * Eg,
+   *     *----A1/P0----*   becomes   *----A1/P0J0----*
+   *                                  `---A1/P0J1---'
+   *
+   * SECOND STAGE (movfeatrmstubs):
+   *
+   * We remove stub moveable edges: whenever, at a node which has only
+   * edges which are part of the same segment, we find an edge on one
+   * side in which is not matched exactly (moveable feature settings
+   * and direction) by an edge on the other side, we delete that extra
+   * edge:
+   *
+   * is split into one node
+   * for each position and direction (directions are
+   * end0-to-side0+end1-to-side1 and end0-to-side1+end1-to-side0), to
+   * which the corresponding edges are connected.  Any resulting node
+   * which has only one edge is removed together with the edge.
+   *
+   * For example (neglecting direction for the purposes of the
+   * example), when we start with:
+   *
+   *             ,---A1/P0J0---.                  
+   *        ...*----A1/P0J1----*---A1/P0J1---*...
+   *            `---A1/P1J0---' `--A1/P1J1--'    
+   *             `--A1/P1J1--'                   
+   *
+   * the edges A1/P0J0 and A1/P1J1 are not matched on the RHS of the node,
+   * so they are removed:
+   *
+   *         ...*----A1/P0J1----*---A1/P0J1---*...
+   *            `---A1/P1J0---' `--A1/P1J1--'    
+   *
+   *
+   * THIRD STAGE:
+   *
+   * We split nodes: every node which has only edges which are part of
+   * the same segment is split into one node for each kind of edge
+   * which goes through it.  The 2nd stage ensures that this is always
+   * straightforward.
+   *
+   * Our example above turns into:
+   *
+   *         ...*----A1/P0J1----*---A1/P0J1---*...
+   *            `---A1/P1J0----*---A1/P1J1--'
    */
 
-  for (;;) {
-    pass++;
-    for (node=all_nodes.head; node; node=node->next) {
-      for (rightside=0; rightside<2; rightside++) {
-       trace("extendsplit pass %d node ",pass); trace_node(node);
-       trace("/%d ",rightside);
-       if ((n= count_edges(&node->sides[!rightside])) != 1) {
-         trace("no: lhs edges=%d\n", n);
-         goto no_extend_this;
+static void movfeatsplitedges(void) {
+  Node *node;
+  int side, l, i, weight, combpos, end;
+  EdgeEnd *edgeend;
+  Edge *edge, *newedge;
+  Segment *segment;
+  MovFeat *subseg, *starfeature, *loopfeat;
+  char *featstr;
+  
+  trace("movfeatsplitedges ...\n");
+  FOR_ALL_NODES(node) {
+    FOR_EDGES(edge, node,side,edgeend) {
+      subseg= edge->subseg;
+      segment= subseg->segment;
+
+      trace("movfeatsplitedges "); trace_edge(edge);
+      trace(" segment %s ", segment->segname);
+      if (segment->n_movfeats<=1) { trace("no moveables\n"); continue; }
+      if (subseg == segment->starfeature) { trace("*-feature\n"); continue; }
+
+      starfeature= segment->starfeature;
+      if (starfeature) {
+       trace("old");
+      } else {
+       trace("new");
+       starfeature= MMALLOC(sizeof(*starfeature));
+       starfeature->segment= segment;
+
+       l=2;  starfeature->n_positions=1;
+       FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
+         if (!loopfeat->movfeat) continue;
+         l += strlen(loopfeat->movfeat);
+         starfeature->n_positions *= loopfeat->n_positions;
        }
-       leftedge= node->sides[!rightside].head;
-       if (leftedge->edge->subseg->movfeat) {
-         trace("no: lhs moveable\n");
-         goto no_extend_this;
+
+       starfeature->movfeat= featstr= MMALLOC(l);
+       strcpy(featstr, "*");
+
+       FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
+         if (!loopfeat->movfeat) continue;
+         strcat(featstr, loopfeat->movfeat);
        }
-       if (!node->sides[rightside].head) {
-         trace("no: rhs absent\n");
-         goto no_extend_this;
+
+       segment->starfeature= starfeature;
+      }
+      trace(" %s(%d) ", starfeature->movfeat, starfeature->n_positions);
+
+      if (subseg->movfeat) {
+       weight= 1;
+       FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
+         if (loopfeat == subseg) goto found;
+         weight *= loopfeat->n_positions;
        }
-       for (rightedge= node->sides[rightside].head;
-            rightedge;
-            rightedge= rightedge->next) {
-         if (!rightedge->edge->subseg->movfeat) {
-           trace("no: rhs fixed ("); trace_edgeend(rightedge); trace(")\n");
-           goto no_extend_this;
-         }
-         if (leftedge->edge->subseg->segment !=
-             rightedge->edge->subseg->segment) {
-           trace("no: lhs seg %s, rhs seg %s (",
-                 leftedge->edge->subseg->segment->segname,
-                 rightedge->edge->subseg->segment->segname);
-           trace_edgeend(rightedge); trace(")\n");
-           goto no_extend_this;
-         }
+       abort();
+      found:
+       trace("%s(x%d)",subseg->movfeat,weight);
+      } else {
+       weight= 1;
+       trace("fixed");
+      }
+
+      trace(" ...\n");
+       
+      for (combpos=0; combpos<starfeature->n_positions; combpos++) {
+       trace("movfeatsplitedges  "); trace_combpos(combpos,segment);
+       if ((combpos / weight) % subseg->n_positions != edge->movpos) {
+         trace(" excluded\n");
+         continue;
        }
-       goto extend_this;
+       newedge= edge_create(masprintf("%s/*%d",edge->pname,combpos),
+                            edge->distance,
+                            starfeature,
+                            combpos);
+       FOR_BOTH(end)
+         edgeend_attach(&newedge->ends[end], edge->ends[end].node);
+      }
+      edge_delete(edgeend);
+    }
+  }
+  trace("movfeatsplitedges complete.\n");
+}
 
-      no_extend_this:;
+static int movfeat_isinnernode(Node *node) {
+  int side;
+  EdgeEnd *edgeend;
+  Segment *all_segment, *segment;
+
+  trace_node(node);
+  all_segment= 0;
+  FOR_BOTH(side) {
+    if (!node->sides[side].head) {
+      trace(" %c terminus\n", "OI"[side]);
+      return 0;
+    }
+  }
+  FOR_NODE_EDGEENDS(side,edgeend, node) {
+    segment= edgeend->edge->subseg->segment;
+    if (!all_segment) {
+      all_segment= segment;
+    } else if (all_segment != segment) {
+      trace(" not inner: %s and %s\n",
+           all_segment->segname, segment->segname);
+      return 0;
+    }
+  }
+  if (!all_segment) {
+    trace(" not inner: no edges?!\n");
+    return 0;
+  }
+  trace(" inner %s ",all_segment->segname);
+  return 1;
+}
+
+static EdgeEnd *movfeat_findmate(EdgeEnd *key) {
+  Node *node;
+  EdgeEnd *mate;
+  int mateside;
+
+  node= key->node->node;
+  mateside= !key->node->side;
+  
+  FOR_SIDE_EDGEENDS(mate, node,mateside) {
+    if (key->edge->subseg == mate->edge->subseg &&
+       key->edge->movpos == mate->edge->movpos &&
+       key->end != mate->end)
+      return mate;
+  }
+  return 0; /* no mate - must be a stub */
+}
+
+static void movfeatrmstubs(void) {
+  int pass, anychange, side;
+  Node *node;
+  Edge *edge;
+  EdgeEnd *edgeend, *mate;
+
+  pass= 0;
+  do {
+    anychange= 0;
+    pass++;
+    trace("movfeatrmstubs pass %d ...\n", pass);
+    FOR_ALL_NODES(node) {
+      trace("movfeatrmstubs ");
+      if (!movfeat_isinnernode(node)) continue;
+      FOR_NODE_EDGEENDS(side,edgeend, node) {
+       edge= edgeend->edge;
+       mate= movfeat_findmate(edgeend);
+       if (mate) {
+         trace("("); trace_edgeend(edgeend); trace(")");
+         continue;
+       }
+       edge_delete(edgeend);
+       anychange++;
       }
+      trace("\n");
     }
-    return;
-
-  extend_this:
-    trace("yes, lhs "); trace_edgeend(leftedge); trace(":\n");
-    for (rightedge= node->sides[rightside].head;
-        rightedge;
-        rightedge= rightedge->next) {
-      rightedge->edge->distance += leftedge->edge->distance;
-      edgeend_replumb(rightedge, edgeend_otherend(leftedge)->node);
+    trace("movfeatrmstubs pass %d %s\n", pass, anychange?"changed":"alldone");
+  } while (anychange);
+}
+
+static void movfeatsplitnodes(void) {
+  Node *node, *newnode;
+  EdgeEnd *edgeend, *mate;
+  Edge *edge;
+  int side;
+  
+  FOR_ALL_NODES(node) {
+    trace("movfeatsplitnodes ");
+    if (!movfeat_isinnernode(node)) continue;
+    if (!node->sides[0].head->next) {
+      trace("pair-trivial\n");
+      continue;
+    }
+    FOR_NODE_EDGEENDS(side,edgeend, node) {
+      edge= edgeend->edge;
+      mate= movfeat_findmate(edgeend);
+      assert(mate);
+      newnode= node_create(masprintf("%sx%d",node->pname,edge->movpos));
+      edgeend_replumb(mate, &newnode->sides[mate->node->side]);
+      edgeend_replumb(edgeend, &newnode->sides[edgeend->node->side]);
     }
-    edge_delete(leftedge->edge);
-    node_surely_orphaned(node);
-    trace(" extendsplit operation complete\n");
   }
 }
 
-static void elimtrivial(void) {
-  /* Eliminate trivial nodes: ones which have only two edges, which
-   * come in on opposite sides, have the same subsegspec, and with the
-   * two ends identically aligned.
+/*---------- operations - trivial graph redaction etc. ----------*/
+
+static void trivpairnodes(void) {
+  /* Eliminate near-trivial nodes - in this case, ones which have only
+   * two edges, which come in on opposite sides, have the same
+   * subsegspec, and with the two ends identically aligned.
    */
   EdgeEnd *leftedge, *rightedge;
-  Node *node, *next_node;
-  
-  for (node=all_nodes.head; node; node=next_node) {
-    next_node=node->next;
-    trace("elimtrivial node "); trace_node(node); trace(" ");
+  Node *node;
+
+  FOR_ALL_NODES(node) {
+    trace("trivpairnodes node "); trace_node(node); trace(" ");
     if (!(count_edges(&node->sides[0])==1 &&
          count_edges(&node->sides[1])==1)) {
       trace("no, a non-unitary side\n");
       continue;
     }
     leftedge= node->sides[0].head;
-    rightedge= node->sides[0].head;
+    rightedge= node->sides[1].head;
     if (leftedge->edge->subseg != rightedge->edge->subseg) {
       trace("no, (sub)segments differ\n");
       continue;
@@ -219,17 +562,95 @@ static void elimtrivial(void) {
       continue;
     }
     trace(" yes:\n");
+    if (!strchr(rightedge->edge->pname, '+')) {
+      rightedge->edge->pname=
+       masprintf("%s+", rightedge->edge->pname);
+    }
     rightedge->edge->distance += leftedge->edge->distance;
     edgeend_replumb(rightedge, edgeend_otherend(leftedge)->node);
-    edge_delete(leftedge->edge);
+    edge_delete(leftedge);
+    trace(" trivpairnodes operation complete\n");
+  }
+}
+
+static void trivnullnodes(void) {
+  /* Eliminate trivial nodes - ones which have no edges. */
+  Node *node;
+  int side;
+  
+  trace("trivnullnodes ");
+  FOR_ALL_NODES(node) {
+    FOR_BOTH(side) {
+      if (node->sides[side].head)
+       goto non_null;
+    }
     node_surely_orphaned(node);
-    trace(" elimtrivial operation complete\n");
+  non_null:;
   }
-} 
+  trace(" trivnullnodes done.\n");
+}
+
+static void check(const char *why) {
+  Node *node;
+  int alloc_edone, used_edone;
+  typedef struct { Edge *edge; int endsdone; } EdgeDone;
+  EdgeDone *edone, *search_edone;
+  int side;
+  EdgeEnd *edgeend;
+  Edge *edge;
+
+  edone= 0;
+  alloc_edone= used_edone= 0;
+  
+  trace("consistency check (%s) ...\n",why);
+  FOR_ALL_NODES(node) {
+    trace(" consistency "); trace_node(node); trace("\n");
+    LIST_CHECKNODE(all_nodes,node);
+    FOR_BOTH(side) {
+      trace(" consistency "); trace_nodeside(&node->sides[side]); trace("\n");
+      assert(node->sides[side].node == node);
+      assert(node->sides[side].side == side);
+      FOR_SIDE_EDGEENDS(edgeend, node,side) {
+       trace(" consistency "); trace_nodeside(&node->sides[side]);
+       trace(" "); trace_edgeend(edgeend); trace("\n");
+       
+       edge= edgeend->edge;
+       LIST_CHECKNODE(node->sides[side], edgeend);
+       assert(edgeend == &edge->ends[edgeend->end]);
+       assert(edgeend->node == &node->sides[side]);
+       if (used_edone==alloc_edone) {
+         alloc_edone += 2; alloc_edone *= 2;
+         edone= MREALLOC(edone, alloc_edone * sizeof(*edone));
+       }
+       for (search_edone= edone;
+            search_edone < edone + used_edone && search_edone->edge != edge;
+            search_edone++);
+       if (search_edone >= edone + used_edone) {
+         search_edone->edge= edge;
+         search_edone->endsdone= 0;
+         used_edone++;
+       }
+       search_edone->endsdone++;
+       assert(search_edone->endsdone <= 2);
+      }
+    }
+  }
+  for (search_edone= edone;
+       search_edone < edone + used_edone;
+       search_edone++) {
+    trace("consistency "); trace_edge(search_edone->edge);
+    trace(" count=%d\n", search_edone->endsdone);
+    assert(search_edone->endsdone == 2);
+  }
+  trace("consistency check (%s) ok\n",why);
+}
 
 static void consistency(void) {
+  check("command-line request");
 }
 
+/*---------- operations - output ----------*/
+
 static void printforneato(void) {
   Node *node;
   EdgeEnd *edgeend;
@@ -238,34 +659,106 @@ static void printforneato(void) {
   
   output("digraph L {\n");
 
-  for (node=all_nodes.head; node; node=node->next) {
-    output("  n%pO [label=\"O\"];\n"
-          "  n%pI [label=\"+\"];\n",
-          node, node);
-    output("  n%pO -> n%pI [len=0.25 arrowsize=0];\n",
-          node, node);
-    for (side=0; side<2; side++) {
-      for (edgeend=node->sides[side].head; edgeend; edgeend=edgeend->next) {
-       if (edgeend->end) continue; /* do each edge once, from end 0, only */
-       edge= edgeend->edge;
-       output("  n%p%c -> n%p%c [label=\"",
-              edge->ends[0].node->node, "OI"[edge->ends[0].node->side],
-              edge->ends[1].node->node, "OI"[edge->ends[1].node->side]);
-       if (!edge->subseg->segment->segname) {
-         output("?");
-       } else {
-         output(edge->subseg->segment->segname);
-         if (edge->subseg->movfeat) {
-           output("/%s%d",edge->subseg->movfeat,edge->movpos);
-         }
+  FOR_ALL_NODES(node) {
+    output("  n%sO [label=\"%s\"];\n"
+          "  n%sI [label=\"+\"];\n",
+          node->pname, node->pname, node->pname);
+    output("  n%sO -> n%sI [len=0.25 arrowsize=0];\n",
+          node->pname, node->pname);
+    FOR_EDGES(edge, node,side,edgeend) {
+      output("  n%s%c -> n%s%c [label=\"%s:",
+            edge->ends[0].node->node->pname,
+            "OI"[edge->ends[0].node->side],
+            edge->ends[1].node->node->pname,
+            "OI"[edge->ends[1].node->side],
+            edge->pname);
+      if (!edge->subseg->segment->segname) {
+       output("?");
+      } else {
+       output(edge->subseg->segment->segname);
+       if (edge->subseg->movfeat) {
+         output("/%s%d",edge->subseg->movfeat,edge->movpos);
        }
-       output("\"];\n");
       }
+      output(":%.2f\"];\n",edge->distance);
     }
   }
   output("}\n");
 }
 
+static void printforforsafety(void) {
+  Node *node;
+  Edge *edge;
+  EdgeEnd *edgeend;
+  int side, end, weight, i;
+  Segment *segment;
+  MovFeat *movfeat;
+  
+  FOR_ALL_NODES(node) {
+    FOR_EDGES(edge, node,side,edgeend) {
+      output("segment ");
+      FOR_BOTH(end) {
+       output("n%s.%d ",
+              edge->ends[end].node->node->pname,
+              edge->ends[end].node->side);
+      }
+      segment= edge->subseg->segment;
+      output(segment->segname);
+      if (segment->n_movfeats>1) {
+       if (!segment->starfeature) {
+         fprintf(stderr,"printforforsafety before movfeatsplitedges!\n");
+         exit(8);
+       }
+       output("/");
+       weight= 1;
+       FOR_SEGMENT_MOVFEATS(i,movfeat, segment) {
+         if (!movfeat->movfeat) continue;
+         output("%s%d", movfeat->movfeat,
+                (edge->movpos / weight) % movfeat->n_positions);
+         weight *= movfeat->n_positions;
+       }
+       output("*%d", edge->movpos);
+      }
+      output(" %f\n", edge->distance);
+    }
+  }
+  output("end\n");
+}
+
+static void printforlayoutsegjoins(void) {
+  Node *node;
+  EdgeEnd *edgeend;
+  Segment **segmentp, *segment;
+  int side;
+  
+  FOR_ALL_NODES(node) {
+    output("layer ");
+    FOR_BOTH(side)
+      if (!node->sides[side].head) {
+       output("segterminus");
+       goto one_sided;
+      }
+    output("segjoin");
+  one_sided:
+    output("%d\n", (node->layermin + node->layermax)/2);
+
+    output("abs segjoin_%s",node->pname);
+    FOR_NODE_EDGEENDS(side,edgeend, node)
+      output("_%s",edgeend->edge->subseg->segment->segname);
+    output(" %f %f %f\n", node->x,node->y,node->a);
+    FOR_ALL_SEGMENTS(segmentp,segment) segment->u= 0;
+    FOR_NODE_EDGEENDS(side,edgeend, node) {
+      segment= edgeend->edge->subseg->segment;
+      if (segment->u++) continue;
+      output("segend %s\n",segment->segname);
+      output("segcmap %s  0.5 setgray\n",segment->segname);
+    }
+    output("\n");
+  }
+}
+
+/*---------- main program ----------*/
+
 typedef struct {
   const char *name;
   void (*fn)(void);
@@ -274,9 +767,14 @@ typedef struct {
 static const OpInfo opinfos[]= {
 #define OI(x) { #x, x },
   OI(printforneato)
+  OI(printforforsafety)
+  OI(printforlayoutsegjoins)
   OI(consistency)
-  OI(extendsplits)
-  OI(elimtrivial)
+  OI(movfeatsplitedges)
+  OI(movfeatrmstubs)
+  OI(movfeatsplitnodes)
+  OI(trivpairnodes)
+  OI(trivnullnodes)
   { 0,0 }
 };