1 /**
2  * Copyright: Copyright Jason White, 2016
3  * License:   MIT
4  * Authors:   Jason White
5  */
6 module button.graph;
7 
8 import std.parallelism : TaskPool;
9 
10 version (unittest)
11 {
12     // Dummy types for testing
13     private struct X { int x; alias x this; }
14     private struct Y { int y; alias y this; }
15     private alias G = Graph!(X, Y);
16 }
17 
18 /**
19  * A bipartite graph.
20  */
21 class Graph(A, B, EdgeDataAB = size_t, EdgeDataBA = size_t)
22     if (!is(A == B))
23 {
24     /**
25      * Find the opposite vertex type of the given vertex type.
26      */
27     alias Opposite(Vertex : A) = B;
28     alias Opposite(Vertex : B) = A; /// Ditto
29 
30     /**
31      * Uniform way of referencing edge data.
32      */
33     alias EdgeData(From : A, To : B) = EdgeDataAB;
34     alias EdgeData(From : B, To : A) = EdgeDataBA;
35 
36     /**
37      * Bookkeeping data associated with each vertex.
38      *
39      * FIXME: A class is only used here such that it can be used with the
40      * synchronized statement. A less heavy-weight implementation should be used
41      * instead.
42      */
43     static class Data
44     {
45         // Number of incoming edges for this vertex.
46         size_t degreeIn;
47 
48         // Number of incoming edges for vertices who have changed or not
49         // changed. The sum of these two variables will always be <= degreeIn.
50         size_t changed, unchanged;
51 
52         void reset()
53         {
54             changed = 0;
55             unchanged = 0;
56         }
57     }
58 
59     private
60     {
61         // Number of incoming edges
62         Data[A] _dataA;
63         Data[B] _dataB;
64 
65         // Edges from A -> B
66         EdgeData!(A, B)[B][A] neighborsA;
67 
68         // Edges from B -> A
69         EdgeData!(B, A)[A][B] neighborsB;
70 
71         // Uniform way of accessing data structures for each vertex type.
72         alias neighbors(Vertex : A) = neighborsA;
73         alias neighbors(Vertex : B) = neighborsB;
74         alias _data(Vertex : A)  = _dataA;
75         alias _data(Vertex : B)  = _dataB;
76     }
77 
78     enum isVertex(Vertex) = is(Vertex : A) || is(Vertex : B);
79     enum isEdge(From, To) = isVertex!From && isVertex!To;
80 
81     /**
82      * Returns the number of vertices for the given type.
83      */
84     @property size_t length(Vertex)() const pure nothrow
85         if (isVertex!Vertex)
86     {
87         return neighbors!Vertex.length;
88     }
89 
90     /**
91      * Returns true if the graph is empty.
92      */
93     @property bool empty() const pure nothrow
94     {
95         return length!A == 0 && length!B == 0;
96     }
97 
98     /**
99      * Returns data associated with the given vertex.
100      */
101     Data data(Vertex)(Vertex v) const pure
102     {
103         return _data!Vertex[v];
104     }
105 
106     /**
107      * Returns the number of edges going into the given vertex.
108      */
109     size_t degreeIn(Vertex)(Vertex v) const pure
110     {
111         return _data!Vertex[v].degreeIn;
112     }
113 
114     /**
115      * Returns the number of edges going out of the given vertex.
116      */
117     size_t degreeOut(Vertex)(Vertex v) const pure
118     {
119         return neighbors!Vertex[v].length;
120     }
121 
122     /**
123      * Adds a vertex if it does not already exist.
124      */
125     void put(Vertex)(Vertex v) pure
126         if (isVertex!Vertex)
127     {
128         if (v !in neighbors!Vertex)
129         {
130             neighbors!Vertex[v] = neighbors!Vertex[v].init;
131             _data!Vertex[v] = new Data();
132         }
133     }
134 
135     /**
136      * Removes a vertex and all the incoming and outgoing edges associated with
137      * it.
138      */
139     void remove(Vertex)(Vertex v) pure
140         if (isVertex!Vertex)
141     {
142         neighbors!Vertex.remove(v);
143         _data!Vertex.remove(v);
144     }
145 
146     unittest
147     {
148         auto g = new G();
149 
150         assert(g.empty);
151 
152         g.put(X(1));
153         g.put(Y(1));
154         g.put(X(1));
155         g.put(Y(1));
156 
157         assert(!g.empty);
158 
159         assert(g.length!X == 1);
160         assert(g.length!Y == 1);
161 
162         g.remove(X(1));
163 
164         assert(g.length!X == 0);
165         assert(g.length!Y == 1);
166 
167         g.remove(Y(1));
168 
169         assert(g.length!X == 0);
170         assert(g.length!Y == 0);
171     }
172 
173     /**
174      * Adds an edge. Both vertices must be added to the graph first.
175      */
176     void put(A from, B to, EdgeDataAB data = EdgeDataAB.init)
177     {
178         assert(from in neighbors!A, "Attempted to add edge from non-existent vertex");
179         assert(to in neighbors!B, "Attempted to add edge to non-existent vertex");
180 
181         if (to !in neighbors!A[from])
182         {
183             neighbors!A[from][to] = data;
184             ++_data!B[to].degreeIn;
185         }
186     }
187 
188     void put(B from, A to, EdgeDataBA data = EdgeDataBA.init)
189     {
190         assert(from in neighbors!B, "Attempted to add edge from non-existent vertex");
191         assert(to in neighbors!A, "Attempted to add edge to non-existent vertex");
192 
193         if (to !in neighbors!B[from])
194         {
195             neighbors!B[from][to] = data;
196             ++_data!A[to].degreeIn;
197         }
198     }
199 
200     unittest
201     {
202         auto g = new G();
203         g.put(X(1));
204         g.put(Y(1));
205         g.put(X(1), Y(1));
206         g.put(X(1), Y(1));
207         assert(g.degreeIn(Y(1)) == 1);
208     }
209 
210     /**
211      * Removes an edge.
212      */
213     void remove(From, To)(From from, To to) pure
214         if (isEdge!(From, To))
215     {
216         assert(to in neighbors!From[from], "Attempted to remove non-existent edge");
217 
218         neighbors!From[from].remove(to);
219         --_data!To[to].degreeIn;
220     }
221 
222     unittest
223     {
224         auto g = new G();
225         g.put(X(1));
226         g.put(Y(1));
227         g.put(X(1), Y(1));
228 
229         assert(g.length!X == 1);
230         assert(g.length!Y == 1);
231         assert(g.degreeIn(Y(1)) == 1);
232 
233         g.remove(X(1), Y(1));
234 
235         assert(g.length!X == 1);
236         assert(g.length!Y == 1);
237         assert(g.degreeIn(Y(1)) == 0);
238     }
239 
240     /**
241      * Returns a range of vertices of the given type.
242      */
243     @property auto vertices(Vertex)() const pure
244         if (isVertex!Vertex)
245     {
246         return neighbors!Vertex.byKey;
247     }
248 
249     static struct Edges(From, To)
250     {
251         import button.edge;
252         alias Neighbors = EdgeData!(From, To)[To][From];
253         alias E = Edge!(From, To, EdgeData!(From, To));
254 
255         private const(Neighbors) _neighbors;
256 
257         this(const(Neighbors) neighbors)
258         {
259             _neighbors = neighbors;
260         }
261 
262         int opApply(int delegate(E) dg) const
263         {
264             int result = 0;
265 
266             foreach (j; _neighbors.byKeyValue())
267             {
268                 foreach (k; j.value.byKeyValue())
269                 {
270                     result = dg(E(j.key, k.key, k.value));
271                     if (result) break;
272                 }
273             }
274 
275             return result;
276         }
277     }
278 
279     /**
280      * Returns an array of edges of the given type.
281      */
282     auto edges(From, To)() const pure
283         if (isEdge!(From, To))
284     {
285         return Edges!(From, To)(neighbors!From);
286     }
287 
288     /**
289      * Returns a range of outgoing neighbors from the given node.
290      */
291     auto outgoing(Vertex)(Vertex v) pure
292         if (isVertex!Vertex)
293     {
294         return neighbors!Vertex[v];
295     }
296 
297     /**
298      * Bookkeeping structure for helping keep track of vertices that have been
299      * visited.
300      */
301     static struct Visited(Value)
302     {
303         Value[A] visitedA;
304         Value[B] visitedB;
305 
306         alias visited(V : A) = visitedA;
307         alias visited(V : B) = visitedB;
308 
309         /**
310          * Adds a vertex to the list of visited vertices.
311          */
312         void put(Vertex)(Vertex v, Value value)
313             if (isVertex!Vertex)
314         {
315             visited!Vertex[v] = value;
316         }
317 
318         /**
319          * Remove a vertex from the list of visited vertices.
320          */
321         void remove(Vertex)(Vertex v)
322             if (isVertex!Vertex)
323         {
324             visited!Vertex.remove(v);
325         }
326 
327         /**
328          * Returns true if the given vertex has been visited.
329          */
330         inout(Value*) opBinaryRight(string op, Vertex)(Vertex v) inout
331             if (op == "in")
332         {
333             return v in visited!Vertex;
334         }
335     }
336 
337     /**
338      * Helper function for starting a traversal from a single vertex.
339      */
340     private void traverse(alias visitThis, alias visitThat, Context, Vertex)
341         (TaskPool pool, Context ctx, Vertex v, bool parentChanged)
342         if (isVertex!Vertex)
343     {
344         import std.parallelism : parallel;
345         import core.atomic : atomicOp;
346 
347         auto data = v in _data!Vertex;
348 
349         assert(data && *data !is null, "Vertex does not exist.");
350 
351         synchronized (*data)
352         {
353             final switch (parentChanged)
354             {
355                 case false: ++data.unchanged; break;
356                 case true: ++data.changed; break;
357             }
358 
359             // Unless everyone has arrived at the junction, do not proceed.
360             if ((data.changed + data.unchanged) < data.degreeIn)
361                 return;
362         }
363 
364         // Reset the counts so that it is safe to traverse the graph again.
365         scope (exit) data.reset();
366 
367         // If none of our dependencies changed, then don't do any work.
368         // Propagate the change status downward.
369         //immutable changed = data.changed > 0 && visitThis(ctx, v, data.degreeIn);
370         immutable changed = visitThis(ctx, v, data.degreeIn, data.changed);
371 
372         Throwable head;
373         Throwable tail;
374 
375         // Visit all our children. If any of them throw up, then catch it, put
376         // it in a bag, and save it for later.
377         foreach (child; pool.parallel(neighbors!Vertex[v].byKey(), 1))
378         {
379             try
380                 traverse!(visitThat, visitThis)(pool, ctx, child, changed);
381             catch (Exception e)
382             {
383                 synchronized (*data)
384                 {
385                     if (head is null)
386                     {
387                         head = e;
388                     }
389                     else
390                     {
391                         e.next = head;
392                         head = e;
393                     }
394                 }
395             }
396         }
397 
398         if (head !is null)
399             throw head;
400     }
401 
402     /**
403      * Traverses the entire graph depth-first calling the given visitor
404      * functions.
405      *
406      * TODO: Use a queue and randomize the build order instead. This should give
407      * better performance on average and help catch race conditions.
408      */
409     void traverse(alias visitA, alias visitB, Context)
410         (Context ctx, TaskPool pool)
411     {
412         import std.parallelism : parallel;
413         import std.algorithm.iteration : filter;
414         import core.sync.mutex : Mutex;
415 
416         auto mutex = new Mutex();
417 
418         Throwable head;
419 
420         foreach (v; pool.parallel(vertices!A.filter!(v => degreeIn(v) == 0), 1))
421         {
422             try
423                 traverse!(visitA, visitB)(pool, ctx, v, true);
424             catch (Exception e)
425             {
426                 synchronized (mutex)
427                 {
428                     if (head is null)
429                     {
430                         head = e;
431                     }
432                     else
433                     {
434                         e.next = head;
435                         head = e;
436                     }
437                 }
438             }
439         }
440 
441         foreach (v; pool.parallel(vertices!B.filter!(v => degreeIn(v) == 0), 1))
442         {
443             try
444                 traverse!(visitB, visitA)(pool, ctx, v, true);
445             catch (Exception e)
446             {
447                 synchronized (mutex)
448                 {
449                     if (head is null)
450                     {
451                         head = e;
452                     }
453                     else
454                     {
455                         e.next = head;
456                         head = e;
457                     }
458                 }
459             }
460         }
461 
462         if (head !is null)
463             throw head;
464     }
465 
466     /**
467      * Helper function for doing a depth-first search to construct a subgraph.
468      */
469     private void subgraphDFS(Vertex)(Vertex v, typeof(this) g, ref Visited!bool visited)
470     {
471         if (v in visited)
472             return;
473 
474         visited.put(v, true);
475 
476         g.put(v);
477 
478         foreach (child; outgoing(v).byKeyValue())
479         {
480             subgraphDFS(child.key, g, visited);
481             g.put(v, child.key, child.value);
482         }
483     }
484 
485     /**
486      * Creates a subgraph using the given roots. This is done by traversing the
487      * graph and only adding the vertices and edges that we come across.
488      */
489     typeof(this) subgraph(const(A[]) rootsA, const(B[]) rootsB)
490     {
491         auto g = new typeof(return)();
492 
493         Visited!bool visited;
494 
495         foreach (v; rootsA)
496             subgraphDFS(v, g, visited);
497 
498         foreach (v; rootsB)
499             subgraphDFS(v, g, visited);
500 
501         return g;
502     }
503 
504     /**
505      * Returns the set of changes between the vertices in this graph and the
506      * other.
507      */
508     auto diffVertices(Vertex)(const ref typeof(this) other) const
509         if (isVertex!Vertex)
510     {
511         import std.array : array;
512         import std.algorithm.iteration : uniq;
513         import std.algorithm.sorting : sort;
514         import util.change;
515 
516         auto theseVertices = this.neighbors!Vertex.byKey().array.sort().uniq;
517         auto thoseVertices = other.neighbors!Vertex.byKey().array.sort().uniq;
518 
519         return changes(theseVertices, thoseVertices);
520     }
521 
522     /**
523      * Returns the set of changes between the edges in this graph and the other.
524      */
525     auto diffEdges(From, To)(const ref typeof(this) other) const
526         if (isEdge!(From, To))
527     {
528         import std.array : array;
529         import std.algorithm.iteration : uniq;
530         import std.algorithm.sorting : sort;
531         import util.change;
532 
533         auto theseEdges = this.edges!(From, To).array.sort().uniq;
534         auto thoseEdges = other.edges!(From, To).array.sort().uniq;
535 
536         return changes(theseEdges, thoseEdges);
537     }
538 
539     /**
540      * Bookkeeping data for each vertex used by Tarjan's strongly connected
541      * components algorithm.
542      */
543     private static struct TarjanData
544     {
545         // Index of this node.
546         size_t index;
547 
548         // The smallest index of any vertex known to be reachable from this
549         // vertex. If this value is equal to the index of this node, then it is
550         // the root of the strongly connected component (SCC).
551         size_t lowlink;
552 
553         // True if this vertex is currently in the depth-first search stack.
554         bool inStack;
555     }
556 
557     /**
558      * A strongly connected component.
559      */
560     static struct SCC
561     {
562         A[] _verticesA;
563         B[] _verticesB;
564 
565         /**
566          * Uniform access to the vertices.
567          */
568         alias vertices(Vertex : A) = _verticesA;
569         alias vertices(Vertex : B) = _verticesB;
570     }
571 
572     /**
573      * Range of strongly connected components in the graph.
574      */
575     private static struct Tarjan
576     {
577         alias G = Graph!(A, B, EdgeDataAB, EdgeDataBA);
578 
579         private
580         {
581             import std.array : Appender;
582 
583             G _graph;
584 
585             size_t index;
586 
587             TarjanData[A] _dataA;
588             TarjanData[B] _dataB;
589 
590             Appender!(A[]) _stackA;
591             Appender!(B[]) _stackB;
592 
593             alias data(Vertex : A)  = _dataA;
594             alias data(Vertex : B)  = _dataB;
595             alias stack(Vertex : A) = _stackA;
596             alias stack(Vertex : B) = _stackB;
597         }
598 
599         this(G graph)
600         {
601             _graph = graph;
602         }
603 
604         int stronglyConnected(V1)(V1 v, scope int delegate(SCC c) dg)
605         {
606             import std.algorithm.comparison : min;
607             import std.array : appender;
608 
609             alias V2 = Opposite!V1;
610 
611             stack!V1.put(v);
612             data!V1[v] = TarjanData(index, index, true);
613             ++index;
614 
615             auto p = v in data!V1;
616 
617             // Consider successors of this vertex
618             foreach (w; _graph.neighbors!V1[v].byKey())
619             {
620                 auto c = w in data!V2; // Child data
621                 if (!c)
622                 {
623                     // Successor w has not yet been visited, recurse on it
624                     if (immutable result = stronglyConnected(w, dg))
625                         return result;
626 
627                     p.lowlink = min(p.lowlink, data!V2[w].lowlink);
628                 }
629                 else if (c.inStack)
630                 {
631                     // Successor w is on the stack and hence in the current SCC
632                     p.lowlink = min(p.lowlink, c.index);
633                 }
634             }
635 
636             // If v is a root vertex, pop the stacks and generate an SCC
637             if (p.lowlink == p.index)
638             {
639                 auto scc = SCC();
640 
641                 auto sccA = appender!(V1[]);
642                 auto sccB = appender!(V2[]);
643 
644                 while (true)
645                 {
646                     auto successorA = stack!V1.data[stack!V1.data.length-1];
647                     stack!V1.shrinkTo(stack!V1.data.length - 1);
648                     data!V1[successorA].inStack = false;
649                     sccA.put(successorA);
650 
651                     // Only pop the other stack if there is something top pop
652                     // and the top is part of the same SCC.
653                     if (stack!V2.data.length)
654                     {
655                         auto successorB = stack!V2.data[stack!V2.data.length-1];
656 
657                         if (data!V2[successorB].lowlink == p.lowlink)
658                         {
659                             stack!V2.shrinkTo(stack!V2.data.length - 1);
660                             data!V2[successorB].inStack = false;
661                             sccB.put(successorB);
662                         }
663                     }
664 
665                     // Pop the stack until we get back to this vertex
666                     if (successorA == v)
667                         break;
668                 }
669 
670                 scc.vertices!V1 = sccA.data;
671                 scc.vertices!V2 = sccB.data;
672 
673                 if (immutable result = dg(scc))
674                     return result;
675             }
676 
677             return 0;
678         }
679 
680         int opApply(scope int delegate(SCC c) dg)
681         {
682             foreach (v; _graph.vertices!A)
683             {
684                 if (v !in data!A)
685                 {
686                     if (immutable result = stronglyConnected(v, dg))
687                         return result;
688                 }
689             }
690 
691             foreach (v; _graph.vertices!B)
692             {
693                 if (v !in data!B)
694                 {
695                     if (immutable result = stronglyConnected(v, dg))
696                         return result;
697                 }
698             }
699 
700             return 0;
701         }
702     }
703 
704     /**
705      * Using Tarjan's algorithm, returns a range of strongly connected
706      * components (SCCs). Each SCC consists of a list of vertices that are
707      * strongly connected. The SCCs are listed in reverse topological order.
708      *
709      * Time complexity: O(|v| + |E|)
710      *
711      * By filtering for SCCs that consist of more than 1 vertex, we can find and
712      * display all the cycles in a graph.
713      */
714     @property auto tarjan()
715     {
716         return Tarjan(this);
717     }
718 
719     /**
720      * Returns an array of cycles in the graph.
721      */
722     @property immutable(SCC)[] cycles()
723     {
724         import std.array : appender;
725         import std.exception : assumeUnique;
726 
727         auto arr = appender!(SCC[]);
728 
729         foreach (scc; tarjan)
730         {
731             if (scc.vertices!A.length &&
732                 scc.vertices!B.length)
733             {
734                 arr.put(scc);
735             }
736         }
737 
738         return assumeUnique(arr.data);
739     }
740 }
741 
742 unittest
743 {
744     import std.algorithm.comparison : equal;
745     import std.array : array;
746 
747     {
748         auto g = new G();
749         g.put(X(1));
750 
751         assert(g.tarjan.array.equal([G.SCC([X(1)], [])]));
752     }
753 
754     {
755         auto g = new G();
756         g.put(Y(1));
757 
758         assert(g.tarjan.array.equal([G.SCC([], [Y(1)])]));
759     }
760 
761     {
762         // Simplest possible cycle (for a bipartite graph)
763         auto g = new G();
764         g.put(X(1));
765         g.put(Y(1));
766         g.put(X(1), Y(1));
767         g.put(Y(1), X(1));
768 
769         // There should be only 1 connected component, encompassing the entire
770         // graph
771         assert(g.tarjan.array.equal([G.SCC([X(1)], [Y(1)])]));
772     }
773 
774     {
775         auto g = new G();
776         g.put(X(1));
777         g.put(Y(2));
778         g.put(X(3));
779         g.put(Y(4));
780         g.put(Y(5));
781         g.put(X(6));
782 
783         g.put(X(1), Y(2));
784         g.put(Y(2), X(3));
785         g.put(X(3), Y(4));
786         g.put(X(3), Y(5));
787         g.put(Y(4), X(6));
788         g.put(X(6), Y(4));
789         g.put(Y(5), X(1));
790 
791         assert(g.tarjan.array.equal([
792             G.SCC([X(6)], [Y(4)]),
793             G.SCC([X(1), X(3)], [Y(2), Y(5)]),
794         ]));
795     }
796 }
797 
798 unittest
799 {
800     import std.stdio;
801 
802     auto g = new G();
803     g.put(X(1));
804     g.put(Y(1));
805     g.put(X(1), Y(1));
806 
807     auto g2 = g.subgraph([X(1)], [Y(1)]);
808     assert(g2.length!X == 1);
809     assert(g2.length!Y == 1);
810 }
811 
812 unittest
813 {
814     import std.algorithm.comparison : equal;
815     import util.change;
816     import button.edge;
817 
818     alias C = Change;
819 
820     auto g1 = new G();
821     g1.put(X(1));
822     g1.put(X(2));
823     g1.put(Y(1));
824     g1.put(X(1), Y(1));
825     g1.put(X(2), Y(1));
826 
827     auto g2 = new G();
828     g2.put(X(1));
829     g2.put(X(3));
830     g2.put(Y(1));
831     g2.put(Y(2));
832     g2.put(X(1), Y(1));
833 
834     assert(g1.diffVertices!X(g2).equal([
835         C!X(X(1), ChangeType.none),
836         C!X(X(2), ChangeType.removed),
837         C!X(X(3), ChangeType.added),
838     ]));
839 
840     assert(g1.diffVertices!Y(g2).equal([
841         C!Y(Y(1), ChangeType.none),
842         C!Y(Y(2), ChangeType.added),
843     ]));
844 
845     alias E = Edge!(X, Y, size_t);
846 
847     assert(g1.diffEdges!(X, Y)(g2).equal([
848         C!E(E(X(1), Y(1), 0), ChangeType.none),
849         C!E(E(X(2), Y(1), 0), ChangeType.removed),
850     ]));
851 }