I am writing a code to shuffle the edges of a graph according to the Configuration Model. In essence, two edges [(v1,v2) and (v3,v4)] are randomly chosen and swapped [yielding (v1,v3) and (v2,v4)] if
- no self-edge is created [v1 is not v3, and v2 is not v4];
- no multi-edge is created [the edges (v1,v3) and (v2,v4) did not already existed].
I wrote the following code to achieve this
// Instantiates an empty undirected graph.
typedef boost::adjacency_list< boost::setS,
boost::vecS,
boost::undirectedS > graph_t;
graph_t graph(9);
// Adds edges to the graph.
boost::add_edge(0, 1, graph); boost::add_edge(0, 3, graph);
boost::add_edge(0, 5, graph); boost::add_edge(0, 7, graph);
boost::add_edge(1, 2, graph); boost::add_edge(2, 3, graph);
boost::add_edge(2, 4, graph); boost::add_edge(4, 8, graph);
boost::add_edge(5, 7, graph); boost::add_edge(5, 8, graph);
boost::add_edge(6, 7, graph); boost::add_edge(7, 8, graph);
// Number of edges.
unsigned int nb_edges = boost::num_edges(graph);
// Defines a function that give a random edge.
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<int> get_rand_edge(0, nb_edges - 1);
// Descriptors and iterators.
graph_t::vertex_descriptor v1, v2, v3, v4;
graph_t::edge_iterator e1_it, e2_it, e_end;
// Shuffles the edges, with the condition of not creating multiple edges or self-loops.
unsigned int nb_edge_swaps(0);
while(nb_edge_swaps < 10 * nb_edges)
{
// Gets the first edge.
std::tie(e1_it, e_end) = boost::edges(graph);
std::advance(e1_it, get_rand_edge(engine));
v1 = boost::source(*e1_it, graph);
v2 = boost::target(*e1_it, graph);
// Gets the second edge.
std::tie(e2_it, e_end) = boost::edges(graph);
std::advance(e2_it, get_rand_edge(engine));
v3 = boost::source(*e2_it, graph);
v4 = boost::target(*e2_it, graph);
// Avoids self-loops.
if((v1 != v3) && (v2 != v4))
{
// Avoids multiple edge.
if(boost::edge(v1, v3, graph).second == false)
{
// Avoids multiple edge.
if(boost::edge(v2, v4, graph).second == false)
{
// Destroys the old edges.
boost::remove_edge(*e1_it, graph);
boost::remove_edge(boost::edge(v3, v4, graph).first, graph);
// Creates the new edges.
boost::add_edge(v1, v3, graph);
boost::add_edge(v2, v4, graph);
// Counts the number of changes.
++nb_edge_swaps;
}
}
}
}
which seems to work quite well, albeit slowly. I was wondering if there were another clever way to achieve the same task more efficiently. I would like the solution to use the Boost Graph Library, but any ideas are welcomed. Thanks!
Without much guidance, I went and created some comparative benchmarks. Timings wih 90 vertices and 120 edges:
Full sample details (click for interactive charts):
Turns out my intuition about adjacency matrix being faster came out quite the opposite:
Benchmark Code
Using https://github.com/rmartinho/nonius
To create the graphs:
¹ (
nth_edge
below is generic and not efficient for adjacency_matrix).