Navigation

This class implements a Cython (di)graph structure made for efficiency. The
graphs are static, i.e. no add/remove vertex/edges methods are available, nor
can they easily or efficiently be implemented within this data structure.

The data structure, however, is made to save the maximum amount of computations
for graph algorithms whose main operation is to list the out-neighbours of a
vertex (which is precisely what BFS, DFS, distance computations and the
flow-related stuff waste their life on).

The code contained in this module is written C-style. While Sage needs a class
for static graphs (not available today, i.e. 2012-01-13) it is not what we try
to address here. The purpose is efficiency and simplicity.

The data structure is actually pretty simple and compact. short_digraph has
five fields

n (int) – the number of vertices in the graph.

m (int) – the number of edges in the graph.

edges (uint32_t*) – array whose length is the number of edges of
the graph.

neighbors (uint32_t**) – this array has size \(n+1\), and
describes how the data of edges should be read : the neighbors of
vertex \(i\) are the elements of edges addressed by
neighbors[i]...neighbors[i+1]-1. The element neighbors[n], which
corresponds to no vertex (they are numbered from \(0\) to \(n-1\)) is present
so that it remains easy to enumerate the neighbors of vertex \(n-1\) : the
last of them is the element addressed by neighbors[n]-1.

edge_labels – this cython list associates a label to each edge of the
graph. If a given edge is represented by edges[i], this its associated
label can be found at edge_labels[i]. This object is usually NULL,
unless the call to init_short_digraph explicitly requires the labels
to be stored in the data structure.

In the example given above, vertex 0 has 2,3,5,7,8 and 9 as out-neighbors, but
not 4, which is an out-neighbour of vertex 1. Vertex \(n-1\) has 2, 5, 8 and 9 as
out-neighbors. \(\text{neighbors[n]}\) points toward the cell immediately after
the end of \(\text{edges}\), hence outside of the allocated memory. It is used
to indicate the end of the outneighbors of vertex \(n-1\)

Iterating over the edges

This is the one thing to have in mind when working with this data structure:

cdef list_edges(short_digraph g):
cdef int i, j
for i in range(g.n):
for j in range(g.neighbors[i+1]-g.neighbors[i]):
print "There is an edge from",str(i),"to",g.neighbors[i][j]

Advantages

Two great points :

The neighbors of a vertex are C types, and are contiguous in memory.

Storing such graphs is incredibly cheaper than storing Python structures.

Well, I think it would be hard to have anything more efficient than that to
enumerate out-neighbors in sparse graphs ! :-)

When creating a fast_digraph from a Graph or DiGraph named
G, the \(i^{\text{th}}\) vertex corresponds to G.vertices()[i]

Some methods return bitset_t objets when lists could be
expected. There is a very useful bitset_list function for this kind of
problems :-)

When the edges are labelled, most of the space taken by this graph is
taken by edge labels. If no edge is labelled then this space is not
allocated, but if any edge has a label then a (possibly empty) label is
stored for each edge, which can double the memory needs.

The data structure stores the number of edges, even though it appears that
this number can be reconstructed with
g.neighbors[n]-g.neighbors[0]. The trick is that not all elements of
the g.edges array are necessarily used : when an undirected graph
contains loops, only one entry of the array of size \(2m\) is used to store
it, instead of the expected two. Storing the number of edges is the only
way to avoid an uselessly costly computation to obtain the number of edges
of an undirected, looped, AND labelled graph (think of several loops on
the same vertex with different labels).

The codes of this module are well documented, and many answers can be
found directly in the code.

Assuming bitset_treached has size at least g.n, this method updates
scc so that it represents the vertices of the strongly connected
component containing v in g. The variable g_reversed is assumed
to represent the reverse of g.