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 }