1 /**
2     This module contains graph algorithms.
3 
4     Copyright: © 2021 Arne Ludwig <arne.ludwig@posteo.de>
5     License: Subject to the terms of the MIT license, as written in the
6              included LICENSE file.
7     Authors: Arne Ludwig <arne.ludwig@posteo.de>
8 */
9 module dalicious.algorithm.graph;
10 
11 import dalicious.math : NaturalNumberSet;
12 import dalicious.container : RingBuffer;
13 import std.algorithm;
14 import std.functional;
15 import std.range;
16 import std.traits;
17 
18 
19 struct HopcroftKarpImpl(node_t, nodes_u_it, nodes_v_it, adjacency_t, count_t = size_t)
20     if (
21         isIntegral!node_t && isUnsigned!node_t &&
22         isForwardRange!nodes_u_it && is(ElementType!nodes_u_it == node_t) &&
23         isForwardRange!nodes_v_it && is(ElementType!nodes_v_it == node_t) &&
24         isRandomAccessRange!adjacency_t && isForwardRange!(ElementType!adjacency_t) &&
25         is(ElementType!(ElementType!adjacency_t) == node_t) &&
26         isIntegral!count_t && isUnsigned!count_t
27     )
28 {
29     struct nil_t { }
30     enum NIL = nil_t();
31     enum inf = count_t.max;
32 
33     private
34     {
35         nodes_u_it U;
36         nodes_v_it V;
37         adjacency_t Adj;
38         node_t[node_t] Pair_U;
39         node_t[node_t] Pair_V;
40         count_t[node_t] _Dist;
41         count_t _DistNIL;
42         RingBuffer!node_t Q;
43     }
44 
45 
46     this(nodes_u_it U, nodes_v_it V, adjacency_t adjacency)
47     {
48         this.U = U;
49         this.V = V;
50         this.Adj = adjacency;
51         // TODO allow user to pass a reusable buffer (node_t[])
52         this.Q = RingBuffer!node_t(adjacency.length);
53     }
54 
55 
56     count_t opCall()
57     {
58         count_t matching;
59 
60         while (BFS())
61             foreach (const node_t u; U)
62             {
63                 if (u !in Pair_U)
64                     if (DFS(u))
65                         ++matching;
66             }
67 
68         return matching;
69     }
70 
71 private:
72 
73     bool BFS()
74     {
75         foreach (const node_t u; U)
76         {
77             if (u !in Pair_U)
78             {
79                 Dist(u, 0u);
80                 Q.pushBack(u);
81             }
82             else
83             {
84                 Dist(u, inf);
85             }
86         }
87 
88         Dist(NIL, inf);
89 
90         while (!Q.empty)
91         {
92             const auto u = Q.front;
93             Q.popFront();
94 
95             if (Dist(u) < Dist(NIL))
96                 foreach (const node_t v; Adj[u])
97                 {
98                     if (Dist(Pair_V, v) == inf)
99                     {
100                         Dist(Pair_V, v) = Dist(u) + 1;
101                         if (v in Pair_V)
102                             Q.pushBack(Pair_V[v]);
103                     }
104                 }
105         }
106 
107         return Dist(NIL) < inf;
108     }
109 
110 
111     bool DFS(const node_t u)
112     {
113         foreach (const node_t v; Adj[u])
114         {
115             if (Dist(Pair_V, v) == Dist(u) + 1)
116             {
117                 if(v !in Pair_V || DFS(Pair_V[v]))
118                 {
119                     Pair_V[v] = u;
120                     Pair_U[u] = v;
121 
122                     return true;
123                 }
124             }
125         }
126 
127         Dist(u) = inf;
128 
129         return false;
130     }
131 
132 
133     ref count_t Dist(node_t n)
134     {
135         return _Dist[n];
136     }
137 
138 
139     void Dist(node_t n, count_t c)
140     {
141         _Dist[n] = c;
142     }
143 
144 
145     ref count_t Dist(nil_t)
146     {
147         return _DistNIL;
148     }
149 
150 
151     void Dist(nil_t, count_t c)
152     {
153         _DistNIL = c;
154     }
155 
156 
157     ref count_t Dist(ref node_t[node_t] Pair, node_t n)
158     {
159         if (n in Pair)
160             return Dist(Pair[n]);
161         else
162             return Dist(NIL);
163     }
164 }
165 
166 // test typing
167 unittest
168 {
169     uint[][] adjacency;
170     uint[] U;
171     uint[] V;
172     cast(void) HopcroftKarpImpl!(
173         uint,
174         typeof(U),
175         typeof(V),
176         typeof(adjacency),
177     )(U, V, adjacency);
178 }
179 
180 // test typing
181 unittest
182 {
183     import std.algorithm;
184 
185     auto adjacency = iota(0u).map!(i => iota(0u));
186     auto U = repeat(0u).take(0u);
187     auto V = repeat(0u).take(0u);
188     cast(void) HopcroftKarpImpl!(
189         uint,
190         typeof(U),
191         typeof(V),
192         typeof(adjacency),
193     )(U, V, adjacency);
194 }
195 
196 
197 
198 /// Takes as input a bipartite graph and produces as output a maximum
199 /// cardinality matching – a set of as many edges as possible with the
200 /// property that no two edges share an endpoint.
201 ///
202 /// Calling this function with a non-bipartite graph results in undefined
203 /// behaviour.
204 count_t hopcroftKarp(
205     count_t = size_t,
206     nodes_u_it,
207     nodes_v_it,
208     adjacency_t,
209 )(nodes_u_it U, nodes_v_it V, adjacency_t adjacency)
210     if (
211         isForwardRange!nodes_u_it && isForwardRange!nodes_v_it &&
212         is(ElementType!nodes_u_it == ElementType!nodes_v_it) &&
213         isIntegral!(ElementType!nodes_u_it) && isUnsigned!(ElementType!nodes_u_it) &&
214         isRandomAccessRange!adjacency_t && isForwardRange!(ElementType!adjacency_t) &&
215         is(ElementType!(ElementType!adjacency_t) == ElementType!nodes_u_it) &&
216         isIntegral!count_t && isUnsigned!count_t
217     )
218 {
219     alias node_t = ElementType!nodes_u_it;
220     auto impl = HopcroftKarpImpl!(
221         node_t,
222         nodes_u_it,
223         nodes_v_it,
224         adjacency_t,
225         count_t,
226     )(U, V, adjacency);
227 
228     return impl();
229 }
230 
231 /// ditto
232 count_t hopcroftKarp(
233     count_t = size_t,
234     nodes_u_it,
235     nodes_v_it,
236     adjacency_t,
237     node_t = ElementType!nodes_u_it,
238 )(nodes_u_it U, nodes_v_it V, adjacency_t adjacency, out node_t[node_t] pairU, out node_t[node_t] pairV)
239     if (
240         isForwardRange!nodes_u_it && isForwardRange!nodes_v_it &&
241         is(ElementType!nodes_u_it == ElementType!nodes_v_it) &&
242         isIntegral!(ElementType!nodes_u_it) && isUnsigned!(ElementType!nodes_u_it) &&
243         isRandomAccessRange!adjacency_t && isForwardRange!(ElementType!adjacency_t) &&
244         is(ElementType!(ElementType!adjacency_t) == ElementType!nodes_u_it) &&
245         isIntegral!count_t && isUnsigned!count_t
246     )
247 {
248     auto impl = HopcroftKarpImpl!(
249         node_t,
250         nodes_u_it,
251         nodes_v_it,
252         adjacency_t,
253         count_t,
254     )(U, V, adjacency);
255 
256     auto count = impl();
257     pairU = impl.Pair_U;
258     pairV = impl.Pair_V;
259 
260     return count;
261 }
262 
263 /// Example
264 unittest
265 {
266     //            ____
267     //           /    \.
268     // 0   1   2 | 3   4
269     // |  / \ /| | |\  |
270     // | /   X | / | \ |
271     // |/   / \|/  |  \|
272     // 5   6   7   8   9
273     //
274     uint[][] adjacency;
275     adjacency.length = 10;
276 
277     alias connect = (from, uint[] neighbors...) {
278         adjacency[from] ~= neighbors;
279     };
280 
281     connect(0u,  5u);
282     connect(1u,  5u, 7u);
283     connect(2u,  6u, 7u);
284     connect(3u,  8u, 9u);
285     connect(4u,  7u, 9u);
286     connect(5u,  0u, 1u);
287     connect(6u,  2u);
288     connect(7u,  2u, 4u);
289     connect(8u,  3u);
290     connect(9u,  3u, 4u);
291 
292     auto count = hopcroftKarp(iota(5u), iota(5u, 10u), adjacency);
293 
294     assert(count == 5);
295 }
296 
297 
298 /**
299     Calculate connected components of the graph defined by `hasEdge`.
300 
301     Params:
302         hasEdge = Binary predicate taking two nodes of type `size_t` which is
303                   true iff the first node is adjacent to the second node.
304         n =       Number of nodes in the graph. `hasEdge` must be defined for
305                   every pair of integer in `0 .. n`.
306     Returns: Array of components represented as arrays of node indices.
307 */
308 size_t[][] connectedComponents(alias hasEdge)(size_t n)
309 {
310     alias _hasEdge = binaryFun!hasEdge;
311 
312     auto unvisitedNodes = NaturalNumberSet(n, Yes.addAll);
313     auto nodesBuffer = new size_t[n];
314     auto components = appender!(size_t[][]);
315 
316     while (!unvisitedNodes.empty)
317     {
318         // discover startNode's component by depth-first search
319         auto component = discoverComponent!_hasEdge(unvisitedNodes);
320         // copy component indices to buffer
321         auto restNodesBuffer = component
322             .elements
323             .copy(nodesBuffer);
324         // append component to result
325         components ~= nodesBuffer[0 .. $ - restNodesBuffer.length];
326         // reduce node buffer
327         nodesBuffer = restNodesBuffer;
328 
329     }
330 
331     return components.data;
332 }
333 
334 ///
335 unittest
336 {
337     import std.algorithm :
338         equal,
339         min;
340 
341     //    _____________
342     //   /             \
343     // (0) --- (1) --- (2)     (3) --- (4)
344     enum n = 5;
345     alias connect = (u, v, x, y) => (u == x && v == y) || (u == y && v == x);
346     alias hasEdge = (u, v) => connect(u, v, 0, 1) ||
347                               connect(u, v, 1, 2) ||
348                               connect(u, v, 2, 0) ||
349                               connect(u, v, 3, 4);
350 
351     auto components = connectedComponents!hasEdge(n);
352 
353     assert(equal(components, [
354         [0, 1, 2],
355         [3, 4],
356     ]));
357 }
358 
359 ///
360 unittest
361 {
362     import std.algorithm :
363         equal,
364         min;
365 
366     //    _____________
367     //   /             \
368     // (0) --- (1) --- (2)     (3) --- (4)
369     //   \_____________________/
370     enum n = 5;
371     alias connect = (u, v, x, y) => (u == x && v == y) || (u == y && v == x);
372     alias hasEdge = (u, v) => connect(u, v, 0, 1) ||
373                               connect(u, v, 1, 2) ||
374                               connect(u, v, 2, 0) ||
375                               connect(u, v, 0, 3) ||
376                               connect(u, v, 3, 4);
377 
378     auto components = connectedComponents!hasEdge(n);
379 
380     import std.stdio;
381     assert(equal(components, [
382         [0, 1, 2, 3, 4],
383     ]));
384 }
385 
386 
387 private NaturalNumberSet discoverComponent(alias hasEdge)(ref NaturalNumberSet nodes)
388 {
389     assert(!nodes.empty, "cannot discoverComponent of an empty graph");
390 
391     // prepare component
392     auto component = NaturalNumberSet(nodes.maxElement);
393     // select start node
394     auto currentNode = nodes.minElement;
395 
396     discoverComponent!hasEdge(nodes, currentNode, component);
397 
398     return component;
399 }
400 
401 
402 private void discoverComponent(alias hasEdge)(ref NaturalNumberSet nodes, size_t currentNode, ref NaturalNumberSet component)
403 {
404     // move currentNode from available nodes to the component
405     component.add(currentNode);
406     nodes.remove(currentNode);
407 
408     // try to find successor of current node
409     foreach (nextNode; nodes.elements)
410     {
411         if (hasEdge(currentNode, nextNode))
412         {
413             assert(
414                 hasEdge(nextNode, currentNode),
415                 "connectedComponents may be called only on an undirected graph",
416             );
417             // found successor -> recurse
418 
419             discoverComponent!hasEdge(nodes, nextNode, component);
420         }
421     }
422 }
423 
424 
425 ///
426 struct SingleSourceShortestPathsSolution(weight_t) if (isNumeric!weight_t)
427 {
428     static if (isFloatingPoint!weight_t)
429         enum unconnectedWeight = weight_t.infinity;
430     else
431         enum unconnectedWeight = weight_t.max;
432     enum noPredecessor = size_t.max;
433 
434     ///
435     size_t startNode;
436 
437     ///
438     size_t[] topologicalOrder;
439     private weight_t[] _distance;
440     private size_t[] _predecessor;
441 
442 
443     ///
444     @property size_t numNodes() const pure nothrow @safe
445     {
446         return topologicalOrder.length;
447     }
448 
449 
450     private size_t originalNode(size_t u) const pure nothrow @safe
451     {
452         return topologicalOrder[u];
453     }
454 
455 
456     ///
457     @property const(weight_t)[] distances() const pure nothrow @safe
458     {
459         return _distance[];
460     }
461 
462 
463     ///
464     @property ref weight_t distance(size_t u) pure nothrow @safe
465     {
466         return _distance[u];
467     }
468 
469 
470     ///
471     @property weight_t distance(size_t u) const pure nothrow @safe
472     {
473         return _distance[u];
474     }
475 
476 
477     ///
478     @property bool isConnected(size_t u) const pure nothrow @safe
479     {
480         return distance(u) < unconnectedWeight;
481     }
482 
483 
484     ///
485     @property ref size_t predecessor(size_t u) pure nothrow @safe
486     {
487         return _predecessor[u];
488     }
489 
490 
491     ///
492     @property size_t predecessor(size_t u) const pure nothrow @safe
493     {
494         return _predecessor[u];
495     }
496 
497 
498     ///
499     @property bool hasPredecessor(size_t u) const pure nothrow @safe
500     {
501         return predecessor(u) != noPredecessor;
502     }
503 
504 
505     ///
506     static struct ReverseShortestPath
507     {
508         private const(SingleSourceShortestPathsSolution!weight_t)* _solution;
509         private size_t _to;
510         private size_t _current;
511 
512 
513         private this(const(SingleSourceShortestPathsSolution!weight_t)* solution, size_t to)
514         {
515             this._solution = solution;
516             this._to = to;
517             this._current = solution !is null && solution.isConnected(to)
518                 ? to
519                 : noPredecessor;
520         }
521 
522 
523         @property const(SingleSourceShortestPathsSolution!weight_t) solution() pure nothrow @safe
524         {
525             return *_solution;
526         }
527 
528 
529         ///
530         @property size_t from() const pure nothrow @safe
531         {
532             return _solution.startNode;
533         }
534 
535 
536         ///
537         @property size_t to() const pure nothrow @safe
538         {
539             return _to;
540         }
541 
542 
543         ///
544         @property bool empty() const pure nothrow @safe
545         {
546             return _current == noPredecessor;
547         }
548 
549 
550         ///
551         @property size_t front() const pure nothrow @safe
552         {
553             assert(
554                 !empty,
555                 "Attempting to fetch the front of an empty SingleSourceShortestPathsSolution.ReverseShortestPath",
556             );
557 
558             return _current;
559         }
560 
561 
562         ///
563         void popFront() pure nothrow @safe
564         {
565             assert(!empty, "Attempting to popFront an empty SingleSourceShortestPathsSolution.ReverseShortestPath");
566 
567             this._current = _solution !is null
568                 ? solution.predecessor(_current)
569                 : noPredecessor;
570         }
571 
572 
573         ///
574         @property ReverseShortestPath save() const pure nothrow @safe @nogc
575         {
576             typeof(return) copy;
577             copy._solution = this._solution;
578             copy._to = this._to;
579             copy._current = this._current;
580 
581             return copy;
582         }
583     }
584 
585 
586     /// Traverse shortest path from dest to startNode; empty if `!isConnected(dest)`.
587     ReverseShortestPath reverseShortestPath(size_t dest) const pure nothrow
588     {
589         return ReverseShortestPath(&this, dest);
590     }
591 }
592 
593 
594 /**
595     Calculate all shortest paths in DAG starting at `start`. The
596     functions `hasEdge` and `weight` define the graphs structure and
597     weights, respectively. Nodes are represented as `size_t` integers.
598     The graph must be directed and acyclic (DAG).
599 
600     Params:
601         hasEdge = Binary predicate taking two nodes of type `size_t` which is
602                   true iff the first node is adjacent to the second node.
603         weight =  Binary function taking two nodes of type `size_t` which
604                   returns the weight of the edge between the first and the
605                   second node. The function may be undefined if `hasEdge`
606                   returns false for the given arguments.
607         n =       Number of nodes in the graph. `hasEdge` must be defined for
608                   every pair of integer in `0 .. n`.
609     Throws: NoDAG if a cycle is detected.
610     Returns: SingleSourceShortestPathsSolution
611 */
612 auto dagSingleSourceShortestPaths(alias hasEdge, alias weight)(size_t start, size_t n)
613 {
614     import std.experimental.checkedint;
615 
616     alias _hasEdge = binaryFun!hasEdge;
617     alias _weight = binaryFun!weight;
618     alias weight_t = typeof(_weight(size_t.init, size_t.init));
619     alias saturated = Checked!(weight_t, Saturate);
620 
621     SingleSourceShortestPathsSolution!weight_t result;
622 
623     with (result)
624     {
625         // sort topological
626         topologicalOrder = topologicalSort!_hasEdge(n);
627         alias N = (u) => originalNode(u);
628 
629         _distance = uninitializedArray!(weight_t[])(n);
630         _distance[] = result.unconnectedWeight;
631         _distance[start] = 0;
632         _predecessor = uninitializedArray!(size_t[])(n);
633         _predecessor[] = size_t.max;
634 
635         foreach (u; topologicalOrder.countUntil(start) .. n)
636             foreach (v; u + 1 .. n)
637                 if (_hasEdge(N(u), N(v)))
638                 {
639                     auto vDistance = saturated(distance(N(v)));
640                     auto uDistance = saturated(distance(N(u))) + saturated(_weight(N(u), N(v)));
641 
642                     if (vDistance > uDistance)
643                     {
644                         distance(N(v)) = uDistance.get();
645                         predecessor(N(v)) = N(u);
646                     }
647                 }
648     }
649 
650     return result;
651 }
652 
653 ///
654 unittest
655 {
656     import std.algorithm : equal;
657 
658     //    _____________   _____________
659     //   /             v /             v
660     // (0) --> (1) --> (2)     (3) --> (4)
661     enum n = 5;
662     alias hasEdge = (u, v) => (u + 1 == v && u != 2) ||
663                               (u + 2 == v && u % 2 == 0);
664     alias weight = (u, v) => 1;
665 
666     auto shortestPaths = dagSingleSourceShortestPaths!(hasEdge, weight)(0, n);
667 
668     assert(equal(shortestPaths.reverseShortestPath(4), [4, 2, 0]));
669     assert(shortestPaths.distance(4) == 2);
670     assert(equal(shortestPaths.reverseShortestPath(2), [2, 0]));
671     assert(shortestPaths.distance(2) == 1);
672     assert(equal(shortestPaths.reverseShortestPath(1), [1, 0]));
673     assert(shortestPaths.distance(1) == 1);
674     assert(equal(shortestPaths.reverseShortestPath(3), size_t[].init));
675     assert(!shortestPaths.isConnected(3));
676 }
677 
678 
679 /**
680     Sort nodes of a DAG topologically. The graph structure is defined by
681     `hasEdge` and `n`. Nodes are represented as `size_t` integers. The graph
682     must be directed and acyclic (DAG).
683 
684     Params:
685         hasEdge = Binary predicate taking two nodes of type `size_t` which is
686                   true iff the first node is adjacent to the second node.
687         n =       Number of nodes in the graph. `hasEdge` must be defined for
688                   every pair of integer in `0 .. n`.
689     Throws: NoDAG if a cycle is detected.
690     Returns: SingleSourceShortestPathsSolution
691 */
692 auto topologicalSort(alias hasEdge)(size_t n)
693 {
694     alias _hasEdge = binaryFun!hasEdge;
695 
696     // list that will contain the sorted nodes
697     auto sortedNodes = new size_t[n];
698 
699     auto sortedNodesHead = sortedNodes[];
700     void enqueueNode(size_t node)
701     {
702         sortedNodesHead[$ - 1] = node;
703         --sortedNodesHead.length;
704     }
705 
706     // keep track which nodes have been visited
707     auto unvisitedNodes = NaturalNumberSet(n, Yes.addAll);
708     auto temporaryVisitedNodes = NaturalNumberSet(n);
709 
710     void visit(size_t node)
711     {
712         if (node !in unvisitedNodes)
713             // already visited
714             return;
715 
716         if (node in temporaryVisitedNodes)
717             // cycle detected
718             throw new NoDAG();
719 
720         temporaryVisitedNodes.add(node);
721 
722         foreach (nextNode; unvisitedNodes.elements)
723             if (_hasEdge(node, nextNode))
724                 visit(nextNode);
725 
726         temporaryVisitedNodes.remove(node);
727         unvisitedNodes.remove(node);
728         enqueueNode(node);
729     }
730 
731     foreach (node; unvisitedNodes.elements)
732         visit(node);
733 
734     return sortedNodes;
735 }
736 
737 ///
738 unittest
739 {
740     import std.algorithm : equal;
741 
742     //    _____________   _____________
743     //   /             v /             v
744     // (0) --> (1) --> (2)     (3) --> (4)
745     enum n = 5;
746     alias hasEdge = (u, v) => (u + 1 == v && u != 2) ||
747                               (u + 2 == v && u % 2 == 0);
748 
749     auto topologicalOrder = topologicalSort!hasEdge(n);
750 
751     assert(equal(topologicalOrder, [3, 0, 1, 2, 4]));
752 }
753 
754 ///
755 unittest
756 {
757     import std.exception : assertThrown;
758 
759     //    _____________   _____________
760     //   /             v /             v
761     // (0) --> (1) --> (2)     (3) --> (4)
762     //   ^_____________________________/
763     enum n = 5;
764     alias hasEdge = (u, v) => (u + 1 == v && u != 2) ||
765                               (u + 2 == v && u % 2 == 0) ||
766                               u == 4 && v == 0;
767 
768     assertThrown!NoDAG(topologicalSort!hasEdge(n));
769 }
770 
771 
772 /// Thrown if a cycle was detected.
773 class NoDAG : Exception
774 {
775     this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
776     {
777         super("not a DAG: graph has cycles", file, line, next);
778     }
779 }
780