Commit 0e21f9a5 authored by Tiago Peixoto's avatar Tiago Peixoto

Improve speed of random_rewire() when parallel edges are not allowed

This also changes the semantics of random_rewire() when parallel edges
/ self loops are forbidden, but the initial graph contains them. Now,
instead of throwing an exception, the rewiring is attempted, with no
new parallel edges / self-loops being created. Under some
circumstances, if the degree sequence is graphical, eventually the
graph can become simple after sufficiently many sweeps.
parent 2e1d1a07
...@@ -66,15 +66,53 @@ target(const pair<size_t, bool>& e, ...@@ -66,15 +66,53 @@ target(const pair<size_t, bool>& e,
} }
template <class Nmap, class Graph>
void add_count(size_t s, size_t t, Nmap& nvmap, Graph& g)
{
if (!is_directed::apply<Graph>::type::value && s > t)
std::swap(s, t);
auto& nmap = nvmap[s];
nmap[t]++;
}
template <class Nmap, class Graph>
void remove_count(size_t s, size_t t, Nmap& nvmap, Graph& g)
{
if (!is_directed::apply<Graph>::type::value && s > t)
std::swap(s, t);
auto& nmap = nvmap[s];
auto iter = nmap.find(t);
iter->second--;
if (iter->second == 0)
nmap.erase(iter);
}
template <class Nmap, class Graph>
size_t get_count(size_t s, size_t t, Nmap& nvmap, Graph& g)
{
if (!is_directed::apply<Graph>::type::value && s > t)
std::swap(s, t);
auto& nmap = nvmap[s];
auto iter = nmap.find(t);
if (iter == nmap.end())
return 0;
return iter->second;
// if (s != t)
// return iter->second;
// else
// return iter->second / 2;
}
// this functor will swap the source of the edge e with the source of edge se // this functor will swap the source of the edge e with the source of edge se
// and the target of edge e with the target of te // and the target of edge e with the target of te
struct swap_edge struct swap_edge
{ {
template <class Graph> template <class Nmap, class Graph>
static bool static bool
parallel_check_target (const pair<size_t, bool>& e, parallel_check_target (const pair<size_t, bool>& e,
const pair<size_t, bool>& te, const pair<size_t, bool>& te,
vector<typename graph_traits<Graph>::edge_descriptor>& edges, vector<typename graph_traits<Graph>::edge_descriptor>& edges,
Nmap& nmap,
const Graph &g) const Graph &g)
{ {
// We want to check that if we swap the target of 'e' with the target of // We want to check that if we swap the target of 'e' with the target of
...@@ -91,11 +129,11 @@ struct swap_edge ...@@ -91,11 +129,11 @@ struct swap_edge
nt = target(te, edges, g), // new target nt = target(te, edges, g), // new target
te_s = source(te, edges, g); // target edge source te_s = source(te, edges, g); // target edge source
if (is_adjacent(s, nt, g)) if (get_count(s, nt, nmap, g) > 0)
return true; // e would clash with an existing edge return true; // e would clash with an existing edge
if (is_adjacent(te_s, t, g)) if (get_count(te_s, t, nmap, g) > 0)
return true; // te would clash with an existing edge return true; // te would clash with an existing edge
return false; // the coast is clear - hooray! return false; // no parallel edges
} }
template <class Graph> template <class Graph>
...@@ -277,7 +315,7 @@ struct graph_rewire ...@@ -277,7 +315,7 @@ struct graph_rewire
random_edge_iter; random_edge_iter;
RewireStrategy<Graph, EdgeIndexMap, CorrProb, BlockDeg> RewireStrategy<Graph, EdgeIndexMap, CorrProb, BlockDeg>
rewire(g, edge_index, edges, corr_prob, bd, cache, rng); rewire(g, edge_index, edges, corr_prob, bd, cache, rng, parallel_edges);
size_t niter; size_t niter;
bool no_sweep; bool no_sweep;
...@@ -344,7 +382,7 @@ public: ...@@ -344,7 +382,7 @@ public:
ErdosRewireStrategy(Graph& g, EdgeIndexMap edge_index, ErdosRewireStrategy(Graph& g, EdgeIndexMap edge_index,
vector<edge_t>& edges, CorrProb, BlockDeg, vector<edge_t>& edges, CorrProb, BlockDeg,
bool, rng_t& rng) bool, rng_t& rng, bool parallel_edges)
: _g(g), _edge_index(edge_index), _edges(edges), : _g(g), _edge_index(edge_index), _edges(edges),
_vertices(HardNumVertices()(g)), _rng(rng) _vertices(HardNumVertices()(g)), _rng(rng)
{ {
...@@ -390,6 +428,7 @@ private: ...@@ -390,6 +428,7 @@ private:
}; };
// this is the mother class for edge-based rewire strategies // this is the mother class for edge-based rewire strategies
// it contains the common loop for finding edges to swap, so different // it contains the common loop for finding edges to swap, so different
// strategies need only to specify where to sample the edges from. // strategies need only to specify where to sample the edges from.
...@@ -403,8 +442,25 @@ public: ...@@ -403,8 +442,25 @@ public:
typedef typename EdgeIndexMap::value_type index_t; typedef typename EdgeIndexMap::value_type index_t;
RewireStrategyBase(Graph& g, EdgeIndexMap edge_index, vector<edge_t>& edges, RewireStrategyBase(Graph& g, EdgeIndexMap edge_index, vector<edge_t>& edges,
rng_t& rng) rng_t& rng, bool parallel_edges)
: _g(g), _edge_index(edge_index), _edges(edges), _rng(rng) {} : _g(g), _edge_index(edge_index), _edges(edges), _rng(rng),
_nmap(get(vertex_index, g), num_vertices(g))
{
if (!parallel_edges)
{
typename graph_traits<Graph>::vertex_iterator v, v_end;
for (tie(v, v_end) = boost::vertices(g); v != v_end; ++v)
{
#ifdef HAVE_SPARSEHASH
_nmap[*v].set_empty_key(get_null_key<size_t>()());
_nmap[*v].set_deleted_key(get_null_key<size_t>()() - 1);
#endif
}
for (size_t i = 0; i < edges.size(); ++i)
add_count(source(edges[i], g), target(edges[i], g), _nmap, g);
}
}
bool operator()(size_t ei, bool self_loops, bool parallel_edges) bool operator()(size_t ei, bool self_loops, bool parallel_edges)
{ {
...@@ -416,7 +472,7 @@ public: ...@@ -416,7 +472,7 @@ public:
pair<size_t, bool> e = make_pair(ei, false); pair<size_t, bool> e = make_pair(ei, false);
// rewire target // rewire target
pair<size_t, bool> et = self.get_target_edge(e); pair<size_t, bool> et = self.get_target_edge(e, parallel_edges);
if (!self_loops) // reject self-loops if not allowed if (!self_loops) // reject self-loops if not allowed
{ {
...@@ -428,7 +484,7 @@ public: ...@@ -428,7 +484,7 @@ public:
// reject parallel edges if not allowed // reject parallel edges if not allowed
if (!parallel_edges && (et.first != e.first)) if (!parallel_edges && (et.first != e.first))
{ {
if (swap_edge::parallel_check_target(e, et, _edges, _g)) if (swap_edge::parallel_check_target(e, et, _edges, _nmap, _g))
return false; return false;
} }
...@@ -437,10 +493,22 @@ public: ...@@ -437,10 +493,22 @@ public:
self.update_edge(e.first, false); self.update_edge(e.first, false);
self.update_edge(et.first, false); self.update_edge(et.first, false);
if (!parallel_edges)
{
remove_count(source(e, _edges, _g), target(e, _edges, _g), _nmap, _g);
remove_count(source(et, _edges, _g), target(et, _edges, _g), _nmap, _g);
}
swap_edge::swap_target(e, et, _edges, _g); swap_edge::swap_target(e, et, _edges, _g);
self.update_edge(e.first, true); self.update_edge(e.first, true);
self.update_edge(et.first, true); self.update_edge(et.first, true);
if (!parallel_edges)
{
add_count(source(e, _edges, _g), target(e, _edges, _g), _nmap, _g);
add_count(source(et, _edges, _g), target(et, _edges, _g), _nmap, _g);
}
} }
else else
{ {
...@@ -455,6 +523,16 @@ protected: ...@@ -455,6 +523,16 @@ protected:
EdgeIndexMap _edge_index; EdgeIndexMap _edge_index;
vector<edge_t>& _edges; vector<edge_t>& _edges;
rng_t& _rng; rng_t& _rng;
#ifdef HAVE_SPARSEHASH
typedef google::dense_hash_map<size_t, size_t> nmapv_t;
#else
typedef unordered_map<size_t, size_t> nmapv_t;
#endif
typedef typename property_map_type::apply<nmapv_t,
typename property_map<Graph, vertex_index_t>::type>
::type::unchecked_t nmap_t;
nmap_t _nmap;
}; };
// this will rewire the edges so that the combined (in, out) degree distribution // this will rewire the edges so that the combined (in, out) degree distribution
...@@ -483,10 +561,10 @@ public: ...@@ -483,10 +561,10 @@ public:
RandomRewireStrategy(Graph& g, EdgeIndexMap edge_index, RandomRewireStrategy(Graph& g, EdgeIndexMap edge_index,
vector<edge_t>& edges, CorrProb, BlockDeg, vector<edge_t>& edges, CorrProb, BlockDeg,
bool, rng_t& rng) bool, rng_t& rng, bool parallel_edges)
: base_t(g, edge_index, edges, rng), _g(g) {} : base_t(g, edge_index, edges, rng, parallel_edges), _g(g) {}
pair<size_t,bool> get_target_edge(pair<size_t,bool>& e) pair<size_t,bool> get_target_edge(pair<size_t,bool>& e, bool parallel_edges)
{ {
std::uniform_int_distribution<> sample(0, base_t::_edges.size() - 1); std::uniform_int_distribution<> sample(0, base_t::_edges.size() - 1);
pair<size_t, bool> et = make_pair(sample(base_t::_rng), false); pair<size_t, bool> et = make_pair(sample(base_t::_rng), false);
...@@ -530,8 +608,8 @@ public: ...@@ -530,8 +608,8 @@ public:
CorrelatedRewireStrategy(Graph& g, EdgeIndexMap edge_index, CorrelatedRewireStrategy(Graph& g, EdgeIndexMap edge_index,
vector<edge_t>& edges, CorrProb, BlockDeg blockdeg, vector<edge_t>& edges, CorrProb, BlockDeg blockdeg,
bool, rng_t& rng) bool, rng_t& rng, bool parallel_edges)
: base_t(g, edge_index, edges, rng), _blockdeg(blockdeg), _g(g) : base_t(g, edge_index, edges, rng, parallel_edges), _blockdeg(blockdeg), _g(g)
{ {
#ifdef HAVE_SPARSEHASH #ifdef HAVE_SPARSEHASH
_edges_by_target.set_empty_key(get_null_key<deg_t>()()); _edges_by_target.set_empty_key(get_null_key<deg_t>()());
...@@ -557,7 +635,7 @@ public: ...@@ -557,7 +635,7 @@ public:
} }
} }
pair<size_t,bool> get_target_edge(pair<size_t, bool>& e) pair<size_t,bool> get_target_edge(pair<size_t, bool>& e, bool parallel_edge)
{ {
if (!is_directed::apply<Graph>::type::value) if (!is_directed::apply<Graph>::type::value)
{ {
...@@ -629,8 +707,9 @@ public: ...@@ -629,8 +707,9 @@ public:
ProbabilisticRewireStrategy(Graph& g, EdgeIndexMap edge_index, ProbabilisticRewireStrategy(Graph& g, EdgeIndexMap edge_index,
vector<edge_t>& edges, CorrProb corr_prob, vector<edge_t>& edges, CorrProb corr_prob,
BlockDeg blockdeg, bool cache, rng_t& rng) BlockDeg blockdeg, bool cache, rng_t& rng,
: base_t(g, edge_index, edges, rng), _g(g), _corr_prob(corr_prob), bool parallel_edges)
: base_t(g, edge_index, edges, rng, parallel_edges), _g(g), _corr_prob(corr_prob),
_blockdeg(blockdeg) _blockdeg(blockdeg)
{ {
#ifdef HAVE_SPARSEHASH #ifdef HAVE_SPARSEHASH
...@@ -689,7 +768,8 @@ public: ...@@ -689,7 +768,8 @@ public:
return _blockdeg.get_block(v, g); return _blockdeg.get_block(v, g);
} }
pair<size_t, bool> get_target_edge(pair<size_t, bool>& e) pair<size_t, bool> get_target_edge(pair<size_t, bool>& e,
bool parallel_edges)
{ {
if (!is_directed::apply<Graph>::type::value) if (!is_directed::apply<Graph>::type::value)
{ {
...@@ -710,6 +790,10 @@ public: ...@@ -710,6 +790,10 @@ public:
ep.second = coin(base_t::_rng); ep.second = coin(base_t::_rng);
} }
if (source(e, base_t::_edges, _g) == source(ep, base_t::_edges, _g) ||
target(e, base_t::_edges, _g) == target(ep, base_t::_edges, _g))
return ep; // rewiring is a no-op
deg_t ep_s_deg = get_deg(source(ep, base_t::_edges, _g), _g); deg_t ep_s_deg = get_deg(source(ep, base_t::_edges, _g), _g);
deg_t ep_t_deg = get_deg(target(ep, base_t::_edges, _g), _g); deg_t ep_t_deg = get_deg(target(ep, base_t::_edges, _g), _g);
...@@ -775,9 +859,10 @@ public: ...@@ -775,9 +859,10 @@ public:
AliasProbabilisticRewireStrategy(Graph& g, EdgeIndexMap edge_index, AliasProbabilisticRewireStrategy(Graph& g, EdgeIndexMap edge_index,
vector<edge_t>& edges, CorrProb corr_prob, vector<edge_t>& edges, CorrProb corr_prob,
BlockDeg blockdeg, bool, rng_t& rng) BlockDeg blockdeg, bool, rng_t& rng,
: base_t(g, edge_index, edges, rng), _g(g), _corr_prob(corr_prob), bool parallel_edges)
_blockdeg(blockdeg) : base_t(g, edge_index, edges, rng, parallel_edges), _g(g),
_corr_prob(corr_prob), _blockdeg(blockdeg)
{ {
#ifdef HAVE_SPARSEHASH #ifdef HAVE_SPARSEHASH
...@@ -853,7 +938,6 @@ public: ...@@ -853,7 +938,6 @@ public:
else else
er = max(_in_edges[items[i]].size(), er = max(_in_edges[items[i]].size(),
numeric_limits<double>::min()); numeric_limits<double>::min());
//ps[items[i]] = exp(log(probs[i]) - log(sum) - log(er));
ps[items[i]] = exp(log(probs[i]) - log(sum) - log(er)); ps[items[i]] = exp(log(probs[i]) - log(sum) - log(er));
} }
} }
...@@ -933,7 +1017,8 @@ public: ...@@ -933,7 +1017,8 @@ public:
return _blockdeg.get_block(v, g); return _blockdeg.get_block(v, g);
} }
pair<size_t, bool> get_target_edge(pair<size_t, bool>& e) pair<size_t, bool> get_target_edge(pair<size_t, bool>& e,
bool parallel_edges)
{ {
if (!is_directed::apply<Graph>::type::value) if (!is_directed::apply<Graph>::type::value)
{ {
...@@ -953,6 +1038,9 @@ public: ...@@ -953,6 +1038,9 @@ public:
deg_t nt = iter->second->sample(base_t::_rng); deg_t nt = iter->second->sample(base_t::_rng);
if (_in_edges[nt].empty() && _out_edges[nt].empty())
return e; // reject
pair<size_t, bool> ep; pair<size_t, bool> ep;
std::bernoulli_distribution coin(_in_edges[nt].size() / std::bernoulli_distribution coin(_in_edges[nt].size() /
double(_in_edges[nt].size() + double(_in_edges[nt].size() +
...@@ -976,16 +1064,17 @@ public: ...@@ -976,16 +1064,17 @@ public:
ep = make_pair(epi, true); ep = make_pair(epi, true);
} }
if (source(e, base_t::_edges, _g) == source(ep, base_t::_edges, _g) ||
target(e, base_t::_edges, _g) == target(ep, base_t::_edges, _g))
return ep; // rewiring is a no-op
vertex_t ep_s = source(ep, base_t::_edges, _g); vertex_t ep_s = source(ep, base_t::_edges, _g);
vertex_t ep_t = target(ep, base_t::_edges, _g); vertex_t ep_t = target(ep, base_t::_edges, _g);
if (t == ep_t)
return e; // reject
deg_t ep_s_deg = get_deg(ep_s, _g); deg_t ep_s_deg = get_deg(ep_s, _g);
deg_t ep_t_deg = get_deg(ep_t, _g); deg_t ep_t_deg = get_deg(ep_t, _g);
assert(ep_t_deg == nt); //assert(ep_t_deg == nt);
double pi = get_prob(s_deg, t_deg) + get_prob(ep_s_deg, ep_t_deg); double pi = get_prob(s_deg, t_deg) + get_prob(ep_s_deg, ep_t_deg);
double pf = get_prob(s_deg, ep_t_deg) + get_prob(ep_s_deg, t_deg); double pf = get_prob(s_deg, ep_t_deg) + get_prob(ep_s_deg, t_deg);
...@@ -1144,7 +1233,8 @@ public: ...@@ -1144,7 +1233,8 @@ public:
TradBlockRewireStrategy(Graph& g, EdgeIndexMap edge_index, TradBlockRewireStrategy(Graph& g, EdgeIndexMap edge_index,
vector<edge_t>& edges, CorrProb corr_prob, vector<edge_t>& edges, CorrProb corr_prob,
BlockDeg blockdeg, bool, rng_t& rng) BlockDeg blockdeg, bool, rng_t& rng,
bool parallel_edges)
: _g(g), _edge_index(edge_index), _edges(edges), _corr_prob(corr_prob), : _g(g), _edge_index(edge_index), _edges(edges), _corr_prob(corr_prob),
_blockdeg(blockdeg), _rng(rng), _sampler(nullptr) _blockdeg(blockdeg), _rng(rng), _sampler(nullptr)
...@@ -1210,19 +1300,29 @@ public: ...@@ -1210,19 +1300,29 @@ public:
bool operator()(size_t ei, bool self_loops, bool parallel_edges) bool operator()(size_t ei, bool self_loops, bool parallel_edges)
{ {
const pair<deg_t, deg_t>& deg = _sampler->sample(_rng); typename graph_traits<Graph>::vertex_descriptor s, t;
while (true)
{
const pair<deg_t, deg_t>& deg = _sampler->sample(_rng);
vector<vertex_t>& svs = _vertices[deg.first]; vector<vertex_t>& svs = _vertices[deg.first];
vector<vertex_t>& tvs = _vertices[deg.second]; vector<vertex_t>& tvs = _vertices[deg.second];
std::uniform_int_distribution<size_t> s_sample(0, svs.size() - 1);
std::uniform_int_distribution<size_t> t_sample(0, tvs.size() - 1);
typename graph_traits<Graph>::vertex_descriptor s, t; if (svs.empty() || tvs.empty())
s = svs[s_sample(_rng)]; continue;
t = tvs[t_sample(_rng)];
std::uniform_int_distribution<size_t> s_sample(0, svs.size() - 1);
std::uniform_int_distribution<size_t> t_sample(0, tvs.size() - 1);
s = svs[s_sample(_rng)];
t = tvs[t_sample(_rng)];
break;
}
// reject self-loops if not allowed // reject self-loops if not allowed
if(!self_loops && s == t) if (!self_loops && s == t)
return false; return false;
// reject parallel edges if not allowed // reject parallel edges if not allowed
......
...@@ -776,18 +776,19 @@ def random_rewire(g, model="uncorrelated", n_iter=1, edge_sweep=True, ...@@ -776,18 +776,19 @@ def random_rewire(g, model="uncorrelated", n_iter=1, edge_sweep=True,
no. 1: 016107 (2011) :doi:`10.1103/PhysRevE.83.016107` :arxiv:`1008.3926` no. 1: 016107 (2011) :doi:`10.1103/PhysRevE.83.016107` :arxiv:`1008.3926`
""" """
if not parallel_edges: # if not parallel_edges:
p = label_parallel_edges(g) # p = label_parallel_edges(g)
if p.a.max() != 0: # if p.a.max() != 0:
raise ValueError("Parallel edge detected. Can't rewire " + # raise ValueError("Parallel edge detected. Can't rewire " +
"graph without parallel edges if it " + # "graph without parallel edges if it " +
"already contains parallel edges!") # "already contains parallel edges!")
if not self_loops:
l = label_self_loops(g) # if not self_loops:
if l.a.max() != 0: # l = label_self_loops(g)
raise ValueError("Self-loop detected. Can't rewire graph " + # if l.a.max() != 0:
"without self-loops if it already contains" + # raise ValueError("Self-loop detected. Can't rewire graph " +
" self-loops!") # "without self-loops if it already contains" +
# " self-loops!")
if (vertex_corr is not None and not g.is_directed()) and "blockmodel" not in model: if (vertex_corr is not None and not g.is_directed()) and "blockmodel" not in model:
corr = lambda i, j: vertex_corr(i[1], j[1]) corr = lambda i, j: vertex_corr(i[1], j[1])
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment