1 // Written in the D programming language.
2 
3 /**
4   Basic graph data structures.
5 
6   Authors:   $(LINK2 http://braingam.es/, Joseph Rushton Wakeling)
7   Copyright: Copyright © 2013 Joseph Rushton Wakeling
8   License:   This program is free software: you can redistribute it and/or modify
9              it under the terms of the GNU General Public License as published by
10              the Free Software Foundation, either version 3 of the License, or
11              (at your option) any later version.
12 
13              This program is distributed in the hope that it will be useful,
14              but WITHOUT ANY WARRANTY; without even the implied warranty of
15              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16              GNU General Public License for more details.
17 
18              You should have received a copy of the GNU General Public License
19              along with this program.  If not, see $(LINK http://www.gnu.org/licenses/).
20 
21   Credits:   The basic graph data structure used here is adapted from the library
22              $(LINK2 http://igraph.sourceforge.net/, igraph) by Gábor Csárdi and
23              Tamás Nepusz.
24 */
25 
26 module dgraph.graph;
27 
28 import std.algorithm, std.array, std.exception, std.range, std.traits;
29 import std..string : format;
30 
31 /// Test if G is a Dgraph graph type.
32 template isGraph(G)
33 {
34     static if (!__traits(hasMember, G, "directed") ||
35                !__traits(hasMember, G, "edge") ||
36                !__traits(hasMember, G, "edgeCount") ||
37                !__traits(hasMember, G, "vertexCount") ||
38                !__traits(hasMember, G, "isEdge") ||
39                !__traits(hasMember, G, "edgeID") ||
40                !__traits(hasMember, G, "addEdge") ||
41                !__traits(hasMember, G, "degreeIn") ||
42                !__traits(hasMember, G, "degreeOut") ||
43                !__traits(hasMember, G, "incidentEdgesIn") ||
44                !__traits(hasMember, G, "incidentEdgesOut") ||
45                !__traits(hasMember, G, "neighboursIn") ||
46                !__traits(hasMember, G, "neighboursOut"))
47     {
48         enum bool isGraph = false;
49     }
50     else static if (!isBoolean!(typeof(G.directed)))
51     {
52         enum bool isGraph = false;
53     }
54     else static if (G.directed && (__traits(hasMember, G, "degree") ||
55                                    __traits(hasMember, G, "incidentEdges") ||
56                                    __traits(hasMember, G, "neighbours")))
57     {
58         enum bool isGraph = false;
59     }
60     else static if (!G.directed && (!__traits(hasMember, G, "degree") ||
61                                     !__traits(hasMember, G, "incidentEdges") ||
62                                     !__traits(hasMember, G, "neighbours")))
63     {
64         enum bool isGraph = false;
65     }
66     else static if (!isRandomAccessRange!(ReturnType!(G.incidentEdgesIn)) ||
67                     !isRandomAccessRange!(ReturnType!(G.incidentEdgesOut)))
68     {
69         enum bool isGraph = false;
70     }
71     else static if (!isRandomAccessRange!(ReturnType!(G.neighboursIn)) ||
72                     !isRandomAccessRange!(ReturnType!(G.neighboursOut)))
73     {
74         enum bool isGraph = false;
75     }
76     else static if (!G.directed && (!isRandomAccessRange!(ReturnType!(G.incidentEdges)) ||
77                                     !isRandomAccessRange!(ReturnType!(G.neighbours))))
78     {
79         enum bool isGraph = false;
80     }
81     else
82     {
83         enum bool isGraph = true;
84     }
85 }
86 
87 /// Test if G is a directed graph.
88 template isDirectedGraph(G)
89 {
90     static if (isGraph!G)
91     {
92         enum bool isDirectedGraph = G.directed;
93     }
94     else
95     {
96         enum bool isDirectedGraph = false;
97     }
98 }
99 
100 /// Test if G is an undirected graph.
101 template isUndirectedGraph(G)
102 {
103     static if (isGraph!G)
104     {
105         enum bool isUndirectedGraph = !G.directed;
106     }
107     else
108     {
109         enum bool isUndirectedGraph = false;
110     }
111 }
112 
113 unittest
114 {
115     assert(isGraph!(IndexedEdgeList!true));
116     assert(isGraph!(IndexedEdgeList!false));
117     assert(isDirectedGraph!(IndexedEdgeList!true));
118     assert(!isDirectedGraph!(IndexedEdgeList!false));
119     assert(!isUndirectedGraph!(IndexedEdgeList!true));
120     assert(isUndirectedGraph!(IndexedEdgeList!false));
121 
122     assert(isGraph!(CachedEdgeList!true));
123     assert(isGraph!(CachedEdgeList!false));
124     assert(isDirectedGraph!(CachedEdgeList!true));
125     assert(!isDirectedGraph!(CachedEdgeList!false));
126     assert(!isUndirectedGraph!(CachedEdgeList!true));
127     assert(isUndirectedGraph!(CachedEdgeList!false));
128 }
129 
130 /**
131  * Graph data type based on igraph's igraph_t.  The basic data structure is a
132  * pair of arrays whose entries consist of respectively the source (tail) and
133  * destination (head) vertices of the edges in the graph.  These are extended
134  * by sorted indices and cumulative sums that enable fast calculation of graph
135  * properties from the stored data.
136  */
137 final class IndexedEdgeList(bool dir)
138 {
139   private:
140     size_t[] tail_;
141     size_t[] head_;
142     size_t[] indexTail_;
143     size_t[] indexHead_;
144     size_t[] sumTail_ = [0];
145     size_t[] sumHead_ = [0];
146 
147     void indexEdgesInsertion()
148     {
149         assert(indexTail_.length == indexHead_.length);
150         assert(tail_.length == head_.length);
151         immutable size_t l = indexTail_.length;
152         indexTail_.length = tail_.length;
153         indexHead_.length = head_.length;
154         foreach (immutable e; l .. tail_.length)
155         {
156             size_t i, j, lower, upper;
157             upper = indexTail_[0 .. e].map!(a => tail_[a]).assumeSorted.lowerBound(tail_[e] + 1).length;
158             lower = indexTail_[0 .. upper].map!(a => tail_[a]).assumeSorted.lowerBound(tail_[e]).length;
159             i = lower + indexTail_[lower .. upper].map!(a => head_[a]).assumeSorted.lowerBound(head_[e]).length;
160             for (j = e; j > i; --j)
161             {
162                 indexTail_[j] = indexTail_[j - 1];
163             }
164             indexTail_[i] = e;
165 
166             upper = indexHead_[0 .. e].map!(a => head_[a]).assumeSorted.lowerBound(head_[e] + 1).length;
167             lower = indexHead_[0 .. upper].map!(a => head_[a]).assumeSorted.lowerBound(head_[e]).length;
168             i = lower + indexHead_[lower .. upper].map!(a => tail_[a]).assumeSorted.lowerBound(tail_[e]).length;
169             for (j = e; j > i; --j)
170             {
171                 indexHead_[j] = indexHead_[j - 1];
172             }
173             indexHead_[i] = e;
174         }
175         assert(indexTail_.length == indexHead_.length);
176         assert(indexTail_.length == tail_.length, format("%s tail indices but %s tail values.", indexTail_.length, tail_.length));
177         assert(indexHead_.length == head_.length, format("%s head indices but %s head values.", indexHead_.length, head_.length));
178     }
179 
180     void indexEdgesSort()
181     {
182         indexTail_ ~= iota(indexTail_.length, tail_.length).array;
183         indexHead_ ~= iota(indexHead_.length, head_.length).array;
184         assert(indexTail_.length == indexHead_.length);
185         indexTail_.multiSort!((a, b) => tail_[a] < tail_[b], (a, b) => head_[a] < head_[b]);
186         indexHead_.multiSort!((a, b) => head_[a] < head_[b], (a, b) => tail_[a] < tail_[b]);
187     }
188 
189     void sumEdges(ref size_t[] sum, in size_t[] vertex, in size_t[] index) @safe const nothrow pure
190     {
191         assert(sum.length > 1);
192 
193         size_t v = vertex[index[0]];
194         sum[0 .. v + 1] = 0;
195         for (size_t i = 1; i < index.length; ++i)
196         {
197             size_t n = vertex[index[i]] - vertex[index[sum[v]]];
198             sum[v + 1 .. v + n + 1] = i;
199             v += n;
200         }
201         sum[v + 1 .. $] = vertex.length;
202     }
203 
204   public:
205     /**
206      * Add new edges to the graph.  These may be provided either singly, by
207      * passing an individual (tail, head) pair, or en masse by passing an array
208      * whose entries are [tail1, head1, tail2, head2, ...].  Duplicate edges
209      * are permitted.
210      */
211     void addEdge()(size_t tail, size_t head)
212     {
213         enforce(tail < vertexCount, format("Edge tail %s is greater than largest vertex ID %s", tail, vertexCount - 1));
214         enforce(head < vertexCount, format("Edge head %s is greater than largest vertex ID %s", head, vertexCount - 1));
215         static if (!directed)
216         {
217             if (head < tail)
218             {
219                 swap(tail, head);
220             }
221         }
222         tail_ ~= tail;
223         head_ ~= head;
224         indexEdgesInsertion();
225         ++sumTail_[tail + 1 .. $];
226         ++sumHead_[head + 1 .. $];
227     }
228 
229     /// ditto
230     void addEdge(T : size_t)(T[] edgeList)
231     {
232         enforce(edgeList.length % 2 == 0);
233         assert(tail_.length == head_.length);
234         immutable size_t l = tail_.length;
235         tail_.length += edgeList.length / 2;
236         head_.length += edgeList.length / 2;
237         foreach (immutable i; 0 .. edgeList.length / 2)
238         {
239             size_t tail = edgeList[2 * i];
240             size_t head = edgeList[2 * i + 1];
241             enforce(tail < vertexCount, format("Edge tail %s is greater than largest vertex ID %s", tail, vertexCount - 1));
242             enforce(head < vertexCount, format("Edge head %s is greater than vertex count %s", head, vertexCount - 1));
243             static if (!directed)
244             {
245                 if (head < tail)
246                 {
247                     swap(tail, head);
248                 }
249             }
250             tail_[l + i] = tail;
251             head_[l + i] = head;
252         }
253         indexEdgesSort();
254         sumEdges(sumTail_, tail_, indexTail_);
255         sumEdges(sumHead_, head_, indexHead_);
256     }
257 
258     static if (directed)
259     {
260         /**
261          * Provide respectively the in- and out-degrees of a vertex v, i.e.
262          * the number of vertices to which v is connected by respectively
263          * incoming or outgoing links.  If the graph is undirected, these
264          * values are identical and the general degree method is also defined.
265          */
266         size_t degreeIn(in size_t v) @safe const pure
267         {
268             enforce(isVertex(v));
269             assert(v + 1 < sumHead_.length);
270             return sumHead_[v + 1] - sumHead_[v];
271         }
272 
273         ///ditto
274         size_t degreeOut(in size_t v) @safe const pure
275         {
276             enforce(isVertex(v));
277             assert(v + 1 < sumTail_.length);
278             return sumTail_[v + 1] - sumTail_[v];
279         }
280     }
281     else
282     {
283         /// Provides the degree of a vertex v in an undirected graph.
284         size_t degree(in size_t v) @safe const pure
285         {
286             enforce(isVertex(v));
287             assert(v + 1 < sumTail_.length);
288             assert(sumTail_.length == sumHead_.length);
289             return (sumTail_[v + 1] - sumTail_[v])
290                  + (sumHead_[v + 1] - sumHead_[v]);
291         }
292 
293         alias degreeIn = degree;
294         alias degreeOut = degree;
295     }
296 
297     /**
298      * Static boolean value indicating whether or not the graph is directed.
299      * This is available for compile-time as well as runtime checks.
300      */
301     alias directed = dir;
302 
303     /**
304      * Returns a list of all edges in the graph in the form of a list of
305      * (tail, head) vertex pairs.
306      */
307     auto edge() @property @safe const nothrow pure
308     {
309         return zip(tail_, head_);
310     }
311 
312     /// Total number of edges in the graph.
313     size_t edgeCount() @property @safe const nothrow pure
314     {
315         assert(tail_.length == head_.length);
316         return tail_.length;
317     }
318 
319     /**
320      * Returns the edge index for a given (tail, head) vertex pair.  If
321      * (tail, head) is not an edge, will throw an exception.
322      */
323     size_t edgeID(size_t tail, size_t head) const
324     {
325         if (!isVertex(tail))
326         {
327             throw new Exception(format("No vertex with ID %s", tail));
328         }
329 
330         if (!isVertex(head))
331         {
332             throw new Exception(format("No vertex with ID %s", head));
333         }
334 
335         static if (!directed)
336         {
337             if (head < tail)
338             {
339                 swap(tail, head);
340             }
341         }
342 
343         size_t tailDeg = sumTail_[tail + 1] - sumTail_[tail];
344         size_t headDeg = sumHead_[head + 1] - sumHead_[head];
345 
346         if (tailDeg == 0)
347         {
348             static if (directed)
349             {
350                 assert(degreeOut(tail) == 0);
351                 throw new Exception(format("Vertex %s has no outgoing neighbours.", tail));
352             }
353             else
354             {
355                 throw new Exception(format("(%s, %s) is not an edge", tail, head));
356             }
357         }
358 
359         if (headDeg == 0)
360         {
361             static if (directed)
362             {
363                 assert(degreeIn(head) == 0);
364                 throw new Exception(format("Vertex %s has no incoming neighbours.", head));
365             }
366             else
367             {
368                 throw new Exception(format("(%s, %s) is not an edge", tail, head));
369             }
370         }
371 
372         if (tailDeg < headDeg)
373         {
374             // search among the heads of tail
375             foreach (immutable i; iota(sumTail_[tail], sumTail_[tail + 1]).map!(a => indexTail_[a]))
376             {
377                 if (head_[i] == head)
378                 {
379                     assert(tail_[i] == tail);
380                     return i;
381                 }
382             }
383             throw new Exception(format("(%s, %s) is not an edge.", tail, head));
384         }
385         else
386         {
387             // search among the tails of head
388             foreach (immutable i; iota(sumHead_[head], sumHead_[head + 1]).map!(a => indexHead_[a]))
389             {
390                 if (tail_[i] == tail)
391                 {
392                     assert(head_[i] == head);
393                     return i;
394                 }
395             }
396             throw new Exception(format("(%s, %s) is not an edge.", tail, head));
397         }
398     }
399 
400     static if (directed)
401     {
402         /**
403          * Returns the IDs of edges respectively incoming to or outgoing from
404          * the specified vertex v.  If the graph is undirected the two will be
405          * identical and the general method incidentEdges is also defined.
406          */
407         auto incidentEdgesIn(in size_t v) const
408         {
409             enforce(isVertex(v));
410             return iota(sumHead_[v], sumHead_[v + 1]).map!(a => indexHead_[a]);
411         }
412 
413         /// ditto
414         auto incidentEdgesOut(in size_t v) const
415         {
416             enforce(isVertex(v));
417             return iota(sumTail_[v], sumTail_[v + 1]).map!(a => indexTail_[a]);
418         }
419     }
420     else
421     {
422         /// ditto
423         auto incidentEdges(in size_t v) const
424         {
425             enforce(isVertex(v));
426             return chain(iota(sumHead_[v], sumHead_[v + 1]).map!(a => indexHead_[a]),
427                          iota(sumTail_[v], sumTail_[v + 1]).map!(a => indexTail_[a]));
428         }
429 
430         alias incidentEdgesIn  = incidentEdges;
431         alias incidentEdgesOut = incidentEdges;
432     }
433 
434     /**
435      * Checks if a given (tail, head) vertex pair forms an edge in the graph.
436      */
437     bool isEdge(size_t tail, size_t head) const
438     {
439         if (!(isVertex(tail) && isVertex(head)))
440         {
441             return false;
442         }
443 
444         static if (!directed)
445         {
446             if (head < tail)
447             {
448                 swap(tail, head);
449             }
450         }
451 
452         size_t tailDeg = sumTail_[tail + 1] - sumTail_[tail];
453         if (tailDeg == 0)
454         {
455             return false;
456         }
457 
458         size_t headDeg = sumHead_[head + 1] - sumHead_[head];
459         if (headDeg == 0)
460         {
461             return false;
462         }
463 
464         if (tailDeg < headDeg)
465         {
466             // search among the heads of tail
467             foreach (immutable t; iota(sumTail_[tail], sumTail_[tail + 1]).map!(a => head_[indexTail_[a]]))
468             {
469                 if (t == head)
470                 {
471                     return true;
472                 }
473             }
474             return false;
475         }
476         else
477         {
478             // search among the tails of head
479             foreach (immutable h; iota(sumHead_[head], sumHead_[head + 1]).map!(a => tail_[indexHead_[a]]))
480             {
481                 if (h == tail)
482                 {
483                     return true;
484                 }
485             }
486             return false;
487         }
488     }
489 
490     bool isVertex(T : size_t)(in T v) @safe const nothrow pure
491     {
492         static if (isSigned!T)
493         {
494             if (v < 0)
495             {
496                 return false;
497             }
498         }
499 
500         if (v < vertexCount)
501         {
502             return true;
503         }
504 
505         return false;
506     }
507 
508     static if (directed)
509     {
510         /**
511          * Returns the IDs of vertices connected to v via incoming or outgoing
512          * links.  If the graph is undirected the two will be identical and the
513          * general neighbours method is also defined.
514          */
515         auto neighboursIn(in size_t v) const
516         {
517             enforce(isVertex(v));
518             return iota(sumHead_[v], sumHead_[v + 1]).map!(a => tail_[indexHead_[a]]);
519         }
520 
521         /// ditto
522         auto neighboursOut(in size_t v) const
523         {
524             enforce(isVertex(v));
525             return iota(sumTail_[v], sumTail_[v + 1]).map!(a => head_[indexTail_[a]]);
526         }
527     }
528     else
529     {
530         /// ditto
531         auto neighbours(in size_t v) const
532         {
533             enforce(isVertex(v));
534             return chain(iota(sumHead_[v], sumHead_[v + 1]).map!(a => tail_[indexHead_[a]]),
535                          iota(sumTail_[v], sumTail_[v + 1]).map!(a => head_[indexTail_[a]]));
536         }
537 
538         alias neighbors = neighbours;
539         alias neighboursIn  = neighbours;
540         alias neighboursOut = neighbours;
541     }
542 
543     alias neighborsIn = neighboursIn;
544     alias neighborsOut = neighboursOut;
545 
546     /**
547      * Get or set the total number of vertices in the graph.  Will throw an
548      * exception if resetting the number of vertices would delete edges.
549      */
550     size_t vertexCount() @property @safe const nothrow pure
551     {
552         assert(sumTail_.length == sumHead_.length);
553         return sumTail_.length - 1;
554     }
555 
556     /// ditto
557     size_t vertexCount(in size_t n) @property @safe pure
558     {
559         immutable size_t l = sumTail_.length;
560         if (n < (l - 1))
561         {
562             // Check that no edges are lost this way
563             if ((sumTail_[n] != sumTail_[$-1]) ||
564                 (sumHead_[n] != sumHead_[$-1]))
565             {
566                 throw new Exception("Cannot set vertexCount value without deleting edges");
567             }
568             else
569             {
570                 sumTail_.length = n + 1;
571                 sumHead_.length = n + 1;
572             }
573         }
574         else
575         {
576             sumTail_.length = n + 1;
577             sumHead_.length = n + 1;
578             sumTail_[l .. $] = sumTail_[l - 1];
579             sumHead_[l .. $] = sumHead_[l - 1];
580         }
581         return vertexCount;
582     }
583 }
584 
585 /**
586  * An extension of IndexedEdgeList that caches the results of calculations of
587  * various graph properties so as to provide speedier performance.  Provides
588  * the same set of public methods.  This is the recommended data type to use
589  * with Dgraph.
590  */
591 final class CachedEdgeList(bool dir)
592 {
593   private:
594     IndexedEdgeList!dir graph_;
595     size_t[] incidentEdgesCache_;
596     size_t[] neighboursCache_;
597 
598     static if (directed)
599     {
600         const(size_t)[][] incidentEdgesIn_;
601         const(size_t)[][] incidentEdgesOut_;
602         const(size_t)[][] neighboursIn_;
603         const(size_t)[][] neighboursOut_;
604     }
605     else
606     {
607         const(size_t)[][] incidentEdges_;
608         const(size_t)[][] neighbours_;
609     }
610 
611   public:
612     this()
613     {
614         graph_ = new IndexedEdgeList!dir;
615     }
616 
617     alias graph_ this;
618 
619     void addEdge()(size_t tail, size_t head)
620     {
621         graph_.addEdge(tail, head);
622         neighboursCache_.length = 2 * tail_.length;
623         incidentEdgesCache_.length = 2 * tail_.length;
624         static if (directed)
625         {
626             neighboursIn_[] = null;
627             neighboursOut_[] = null;
628             incidentEdgesIn_[] = null;
629             incidentEdgesOut_[] = null;
630         }
631         else
632         {
633             neighbours_[] = null;
634             incidentEdges_[] = null;
635         }
636     }
637 
638     void addEdge(T : size_t)(T[] edgeList)
639     {
640         graph_.addEdge(edgeList);
641         neighboursCache_.length = 2 * tail_.length;
642         incidentEdgesCache_.length = 2 * tail_.length;
643         static if (directed)
644         {
645             neighboursIn_[] = null;
646             neighboursOut_[] = null;
647             incidentEdgesIn_[] = null;
648             incidentEdgesOut_[] = null;
649         }
650         else
651         {
652             neighbours_[] = null;
653             incidentEdges_[] = null;
654         }
655     }
656 
657     static if (directed)
658     {
659         size_t degreeIn(in size_t v) @safe const pure
660         {
661             return graph_.degreeIn(v);
662         }
663 
664         size_t degreeOut(in size_t v) @safe const pure
665         {
666             return graph_.degreeOut(v);
667         }
668     }
669     else
670     {
671         size_t degree(in size_t v) @safe const pure
672         {
673             return graph_.degree(v);
674         }
675 
676         alias degreeIn = degree;
677         alias degreeOut = degree;
678     }
679 
680     alias directed = dir;
681 
682     auto edge() @property @safe const nothrow pure
683     {
684         return graph_.edge;
685     }
686 
687     size_t edgeCount() @property @safe const nothrow pure
688     {
689         return graph_.edgeCount;
690     }
691 
692     size_t edgeID(size_t tail, size_t head) const
693     {
694         return graph_.edgeID(tail, head);
695     }
696 
697     static if (directed)
698     {
699         auto incidentEdgesIn(in size_t v) @safe nothrow pure
700         {
701             if (incidentEdgesIn_[v] is null)
702             {
703                 immutable size_t start = sumHead_[v] + sumTail_[v];
704                 immutable size_t end = sumTail_[v] + sumHead_[v + 1];
705                 size_t j = start;
706                 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1])
707                 {
708                     incidentEdgesCache_[j] = indexHead_[i];
709                     ++j;
710                 }
711                 assert(j == end);
712                 incidentEdgesIn_[v] = incidentEdgesCache_[start .. end];
713             }
714             return incidentEdgesIn_[v];
715         }
716 
717         auto incidentEdgesOut(in size_t v) @safe nothrow pure
718         {
719             if (incidentEdgesOut_[v] is null)
720             {
721                 immutable size_t start = sumTail_[v] + sumHead_[v + 1];
722                 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1];
723                 size_t j = start;
724                 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1])
725                 {
726                     incidentEdgesCache_[j] = indexTail_[i];
727                     ++j;
728                 }
729                 assert(j == end);
730                 incidentEdgesOut_[v] = incidentEdgesCache_[start .. end];
731             }
732             return incidentEdgesOut_[v];
733         }
734     }
735     else
736     {
737         auto incidentEdges(in size_t v) @safe nothrow pure
738         {
739             if (incidentEdges_[v] is null)
740             {
741                 immutable size_t start = sumHead_[v] + sumTail_[v];
742                 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1];
743                 size_t j = start;
744                 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1])
745                 {
746                     incidentEdgesCache_[j] = indexHead_[i];
747                     ++j;
748                 }
749                 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1])
750                 {
751                     incidentEdgesCache_[j] = indexTail_[i];
752                     ++j;
753                 }
754                 assert(j == end);
755                 incidentEdges_[v] = incidentEdgesCache_[start .. end];
756             }
757             return incidentEdges_[v];
758         }
759 
760         alias incidentEdgesIn  = incidentEdges;
761         alias incidentEdgesOut = incidentEdges;
762     }
763 
764     bool isEdge(size_t tail, size_t head) const
765     {
766         return graph_.isEdge(tail, head);
767     }
768 
769     bool isVertex(T : size_t)(in T v) @safe const nothrow pure
770     {
771         return graph_.isVertex(v);
772     }
773 
774     static if (directed)
775     {
776         auto neighboursIn(in size_t v) @safe nothrow pure
777         {
778             if (neighboursIn_[v] is null)
779             {
780                 immutable size_t start = sumHead_[v] + sumTail_[v];
781                 immutable size_t end = sumTail_[v] + sumHead_[v + 1];
782                 size_t j = start;
783                 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1])
784                 {
785                     neighboursCache_[j] = tail_[indexHead_[i]];
786                     ++j;
787                 }
788                 assert(j == end);
789                 neighboursIn_[v] = neighboursCache_[start .. end];
790             }
791             return neighboursIn_[v];
792         }
793 
794         auto neighboursOut(in size_t v) @safe nothrow pure
795         {
796             if (neighboursOut_[v] is null)
797             {
798                 immutable size_t start = sumTail_[v] + sumHead_[v + 1];
799                 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1];
800                 size_t j = start;
801                 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1])
802                 {
803                     neighboursCache_[j] = head_[indexTail_[i]];
804                     ++j;
805                 }
806                 assert(j == end);
807                 neighboursOut_[v] = neighboursCache_[start .. end];
808             }
809             return neighboursOut_[v];
810         }
811     }
812     else
813     {
814         auto neighbours(in size_t v) @safe nothrow pure
815         {
816             if (neighbours_[v] is null)
817             {
818                 immutable size_t start = sumHead_[v] + sumTail_[v];
819                 immutable size_t end = sumHead_[v + 1] + sumTail_[v + 1];
820                 size_t j = start;
821                 foreach (immutable i; sumHead_[v] .. sumHead_[v + 1])
822                 {
823                     neighboursCache_[j] = tail_[indexHead_[i]];
824                     ++j;
825                 }
826                 foreach (immutable i; sumTail_[v] .. sumTail_[v + 1])
827                 {
828                     neighboursCache_[j] = head_[indexTail_[i]];
829                     ++j;
830                 }
831                 assert(j == end);
832                 neighbours_[v] = neighboursCache_[start .. end];
833             }
834             return neighbours_[v];
835         }
836 
837         alias neighbors = neighbours;
838         alias neighboursIn  = neighbours;
839         alias neighboursOut = neighbours;
840     }
841 
842     alias neighborsIn = neighboursIn;
843     alias neighborsOut = neighboursOut;
844 
845     size_t vertexCount() @property @safe const nothrow pure
846     {
847         return graph_.vertexCount;
848     }
849 
850     size_t vertexCount(in size_t n) @property @safe pure
851     {
852         static if (directed)
853         {
854             assert(sumHead_.length == neighboursIn_.length + 1);
855             assert(sumTail_.length == neighboursOut_.length + 1);
856             assert(sumHead_.length == incidentEdgesIn_.length + 1);
857             assert(sumTail_.length == incidentEdgesOut_.length + 1);
858         }
859         else
860         {
861             assert(sumTail_.length == neighbours_.length + 1);
862             assert(sumHead_.length == incidentEdges_.length + 1);
863         }
864 
865         immutable size_t l = sumTail_.length;
866         graph_.vertexCount = n;
867 
868         static if (directed)
869         {
870             neighboursIn_.length = n;
871             neighboursOut_.length = n;
872             incidentEdgesIn_.length = n;
873             incidentEdgesOut_.length = n;
874 
875             if (n >= l)
876             {
877                 neighboursIn_[l - 1 .. $] = null;
878                 neighboursOut_[l - 1 .. $] = null;
879                 incidentEdgesIn_[l - 1 .. $] = null;
880                 incidentEdgesOut_[l - 1 .. $] = null;
881             }
882         }
883         else
884         {
885             neighbours_.length = n;
886             incidentEdges_.length = n;
887 
888             if (n >= l)
889             {
890                 neighbours_[l - 1 .. $] = null;
891                 incidentEdges_[l - 1 .. $] = null;
892             }
893         }
894         return vertexCount;
895     }
896 }
897 
898 unittest
899 {
900     import std.typetuple;
901 
902     foreach (Graph; TypeTuple!(IndexedEdgeList, CachedEdgeList))
903     {
904         foreach (directed; TypeTuple!(true, false))
905         {
906             /* We begin by creating two graphs with the different
907              * addEdge methods and ensuring that they wind up with
908              * the same internal data.
909              */
910             auto g1 = new Graph!directed;
911             auto g2 = new Graph!directed;
912             g1.vertexCount = g2.vertexCount = 10;
913             assert(g1.vertexCount == 10);
914             assert(g2.vertexCount == 10);
915 
916             foreach (v; 0 .. 10)
917             {
918                 assert(g1.isVertex(v), format("%s should be a vertex!"));
919             }
920 
921             foreach (v; 10 .. 20)
922             {
923                 assert(!g1.isVertex(v), format("%s should not be a vertex!"));
924             }
925 
926             g1.addEdge(5, 8);
927             g1.addEdge(5, 4);
928             g1.addEdge(7, 4);
929             g1.addEdge(3, 4);
930             g1.addEdge(6, 9);
931             g1.addEdge(3, 2);
932 
933             g2.addEdge([5, 8, 5, 4, 7, 4, 3, 4, 6, 9, 3, 2]);
934 
935             assert(g1.edgeCount == 6);
936             assert(g2.edgeCount == 6);
937 
938             if (directed)
939             {
940                 assert(g1.tail_ == [5, 5, 7, 3, 6, 3]);
941                 assert(g2.tail_ == [5, 5, 7, 3, 6, 3]);
942                 assert(g1.head_ == [8, 4, 4, 4, 9, 2]);
943                 assert(g2.head_ == [8, 4, 4, 4, 9, 2]);
944 
945                 assert(g1.indexTail_ == [5, 3, 1, 0, 4, 2]);
946                 assert(g2.indexTail_ == [5, 3, 1, 0, 4, 2]);
947                 assert(g1.indexHead_ == [5, 3, 1, 2, 0, 4]);
948                 assert(g2.indexHead_ == [5, 3, 1, 2, 0, 4]);
949 
950                 assert(g1.sumTail_ == [0, 0, 0, 0, 2, 2, 4, 5, 6, 6, 6]);
951                 assert(g2.sumTail_ == [0, 0, 0, 0, 2, 2, 4, 5, 6, 6, 6]);
952                 assert(g1.sumHead_ == [0, 0, 0, 1, 1, 4, 4, 4, 4, 5, 6]);
953                 assert(g2.sumHead_ == [0, 0, 0, 1, 1, 4, 4, 4, 4, 5, 6]);
954             }
955             else
956             {
957                 assert(g1.tail_ == [5, 4, 4, 3, 6, 2]);
958                 assert(g2.tail_ == [5, 4, 4, 3, 6, 2]);
959                 assert(g1.head_ == [8, 5, 7, 4, 9, 3]);
960                 assert(g2.head_ == [8, 5, 7, 4, 9, 3]);
961 
962                 assert(g1.indexTail_ == [5, 3, 1, 2, 0, 4]);
963                 assert(g2.indexTail_ == [5, 3, 1, 2, 0, 4]);
964                 assert(g1.indexHead_ == [5, 3, 1, 2, 0, 4]);
965                 assert(g2.indexHead_ == [5, 3, 1, 2, 0, 4]);
966 
967                 assert(g1.sumTail_ == [0, 0, 0, 1, 2, 4, 5, 6, 6, 6, 6]);
968                 assert(g2.sumTail_ == [0, 0, 0, 1, 2, 4, 5, 6, 6, 6, 6]);
969                 assert(g1.sumHead_ == [0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 6]);
970                 assert(g2.sumHead_ == [0, 0, 0, 0, 1, 2, 3, 3, 4, 5, 6]);
971             }
972 
973             foreach (immutable v; 0 .. g1.vertexCount)
974             {
975                 assert(g1.degreeIn(v) == g2.degreeIn(v));
976                 assert(g1.degreeOut(v) == g2.degreeOut(v));
977                 static if (!directed)
978                 {
979                     assert(g1.degree(v) == g2.degree(v));
980                 }
981             }
982 
983             /* If the above all passes, then we know that the internal data
984              * representation is correct and we can focus on how the graph
985              * transforms this into output.
986              *
987              * Let's start by checking that vertex IDs are recognized ...
988              */
989             foreach (immutable i; -10 .. 20)
990             {
991                 if (0 <= i && i < g1.vertexCount)
992                 {
993                     assert(g1.isVertex(i));
994                 }
995                 else
996                 {
997                     assert(!g1.isVertex(i));
998                 }
999             }
1000 
1001             /* Now check that edges are correctly recognized and that the
1002              * edge ID representation works.
1003              */
1004             foreach (immutable h; -10 .. 20)
1005             {
1006                 foreach (immutable t; -10 .. 20)
1007                 {
1008                     if (directed)
1009                     {
1010                         if ((h == 5 && t == 8) ||
1011                             (h == 5 && t == 4) ||
1012                             (h == 7 && t == 4) ||
1013                             (h == 3 && t == 4) ||
1014                             (h == 6 && t == 9) ||
1015                             (h == 3 && t == 2))
1016                         {
1017                             assert(g1.isEdge(h, t));
1018                             auto i = g1.edgeID(h, t);
1019                             assert(h == g1.tail_[i]);
1020                             assert(t == g1.head_[i]);
1021                         }
1022                         else
1023                         {
1024                             assert(!g1.isEdge(h, t));
1025                             assertThrown(g1.edgeID(h, t));
1026                         }
1027                     }
1028                     else
1029                     {
1030                         if ((h == 5 && t == 8) || (h == 8 && t == 5) ||
1031                             (h == 5 && t == 4) || (h == 4 && t == 5) ||
1032                             (h == 7 && t == 4) || (h == 4 && t == 7) ||
1033                             (h == 3 && t == 4) || (h == 4 && t == 3) ||
1034                             (h == 6 && t == 9) || (h == 9 && t == 6) ||
1035                             (h == 3 && t == 2) || (h == 2 && t == 3))
1036                         {
1037                             assert(g1.isEdge(h, t));
1038                             auto i = g1.edgeID(h, t);
1039                             if (h <= t)
1040                             {
1041                                 assert(h == g1.tail_[i]);
1042                                 assert(t == g1.head_[i]);
1043                             }
1044                             else
1045                             {
1046                                 assert(h == g1.head_[i]);
1047                                 assert(t == g1.tail_[i]);
1048                             }
1049                         }
1050                         else
1051                         {
1052                             assert(!g1.isEdge(h, t));
1053                             assertThrown(g1.edgeID(h, t));
1054                         }
1055                     }
1056                 }
1057             }
1058 
1059             // Another check on edge ID.
1060             foreach (immutable e; 0 .. g1.edgeCount)
1061             {
1062                 size_t h = g1.tail_[e];
1063                 size_t t = g1.head_[e];
1064                 assert(e == g1.edgeID(h, t));
1065                 if (!directed)
1066                 {
1067                     assert(e == g1.edgeID(t, h));
1068                 }
1069             }
1070 
1071             // Let's check the edge and neighbour functions.
1072             foreach (immutable v; 0 .. g1.vertexCount)
1073             {
1074                 foreach (immutable e, immutable n; zip(g1.incidentEdgesOut(v), g1.neighboursOut(v)))
1075                 {
1076                     if (directed || v <= n)
1077                     {
1078                         assert(g1.edge[e][0] == v);
1079                         assert(g1.edge[e][1] == n);
1080                     }
1081                     else
1082                     {
1083                         assert(g1.edge[e][0] == n);
1084                         assert(g1.edge[e][1] == v);
1085                     }
1086                 }
1087 
1088                 foreach (immutable e, immutable n; zip(g1.incidentEdgesIn(v), g1.neighboursIn(v)))
1089                 {
1090                     if (directed || v > n)
1091                     {
1092                         assert(g1.edge[e][0] == n);
1093                         assert(g1.edge[e][1] == v);
1094                     }
1095                     else
1096                     {
1097                         assert(g1.edge[e][0] == v);
1098                         assert(g1.edge[e][1] == n);
1099                     }
1100                 }
1101 
1102                 static if (!directed)
1103                 {
1104                     foreach (immutable e, immutable n; zip(g1.incidentEdges(v), g1.neighbours(v)))
1105                     {
1106                         if (v <= n)
1107                         {
1108                             assert(g1.edge[e][0] == v);
1109                             assert(g1.edge[e][1] == n);
1110                         }
1111                         else
1112                         {
1113                             assert(g1.edge[e][0] == n);
1114                             assert(g1.edge[e][1] == v);
1115                         }
1116                     }
1117                 }
1118             }
1119 
1120             // Check that altering the number of vertices works OK.
1121             g1.vertexCount = 20;
1122             assert(g1.vertexCount == 20);
1123             assertThrown(g1.vertexCount(5));
1124             g1.vertexCount = 10;
1125 
1126             // Check that now we've re-resized it it's back to where it was.
1127             assert(g1.tail_ == g2.tail_);
1128             assert(g1.head_ == g2.head_);
1129             assert(g1.indexTail_ == g2.indexTail_);
1130             assert(g1.indexHead_ == g2.indexHead_);
1131             assert(g1.sumTail_ == g2.sumTail_);
1132             assert(g1.sumHead_ == g2.sumHead_);
1133         }
1134     }
1135 }