diff --git a/src/graph/generation/graph_rewiring.hh b/src/graph/generation/graph_rewiring.hh index 14a1cd4cbafeaccba0b92e1c14b56f7915fa3ca2..c888ebc8f737d340f9c5ce4588a9c0cc02744a14 100644 --- a/src/graph/generation/graph_rewiring.hh +++ b/src/graph/generation/graph_rewiring.hh @@ -66,15 +66,53 @@ target(const pair& e, } +template +void add_count(size_t s, size_t t, Nmap& nvmap, Graph& g) +{ + if (!is_directed::apply::type::value && s > t) + std::swap(s, t); + auto& nmap = nvmap[s]; + nmap[t]++; +} + +template +void remove_count(size_t s, size_t t, Nmap& nvmap, Graph& g) +{ + if (!is_directed::apply::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 +size_t get_count(size_t s, size_t t, Nmap& nvmap, Graph& g) +{ + if (!is_directed::apply::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 // and the target of edge e with the target of te struct swap_edge { - template + template static bool parallel_check_target (const pair& e, const pair& te, vector::edge_descriptor>& edges, + Nmap& nmap, const Graph &g) { // We want to check that if we swap the target of 'e' with the target of @@ -91,11 +129,11 @@ struct swap_edge nt = target(te, edges, g), // new target 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 - 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 false; // the coast is clear - hooray! + return false; // no parallel edges } template @@ -277,7 +315,7 @@ struct graph_rewire random_edge_iter; RewireStrategy - 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; bool no_sweep; @@ -344,7 +382,7 @@ public: ErdosRewireStrategy(Graph& g, EdgeIndexMap edge_index, vector& edges, CorrProb, BlockDeg, - bool, rng_t& rng) + bool, rng_t& rng, bool parallel_edges) : _g(g), _edge_index(edge_index), _edges(edges), _vertices(HardNumVertices()(g)), _rng(rng) { @@ -390,6 +428,7 @@ private: }; + // this is the mother class for edge-based rewire strategies // it contains the common loop for finding edges to swap, so different // strategies need only to specify where to sample the edges from. @@ -403,8 +442,25 @@ public: typedef typename EdgeIndexMap::value_type index_t; RewireStrategyBase(Graph& g, EdgeIndexMap edge_index, vector& edges, - rng_t& rng) - : _g(g), _edge_index(edge_index), _edges(edges), _rng(rng) {} + rng_t& rng, bool parallel_edges) + : _g(g), _edge_index(edge_index), _edges(edges), _rng(rng), + _nmap(get(vertex_index, g), num_vertices(g)) + { + if (!parallel_edges) + { + typename graph_traits::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()()); + _nmap[*v].set_deleted_key(get_null_key()() - 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) { @@ -416,7 +472,7 @@ public: pair e = make_pair(ei, false); // rewire target - pair et = self.get_target_edge(e); + pair et = self.get_target_edge(e, parallel_edges); if (!self_loops) // reject self-loops if not allowed { @@ -428,7 +484,7 @@ public: // reject parallel edges if not allowed 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; } @@ -437,10 +493,22 @@ public: self.update_edge(e.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); self.update_edge(e.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 { @@ -455,6 +523,16 @@ protected: EdgeIndexMap _edge_index; vector& _edges; rng_t& _rng; + +#ifdef HAVE_SPARSEHASH + typedef google::dense_hash_map nmapv_t; +#else + typedef unordered_map nmapv_t; +#endif + typedef typename property_map_type::apply::type> + ::type::unchecked_t nmap_t; + nmap_t _nmap; }; // this will rewire the edges so that the combined (in, out) degree distribution @@ -483,10 +561,10 @@ public: RandomRewireStrategy(Graph& g, EdgeIndexMap edge_index, vector& edges, CorrProb, BlockDeg, - bool, rng_t& rng) - : base_t(g, edge_index, edges, rng), _g(g) {} + bool, rng_t& rng, bool parallel_edges) + : base_t(g, edge_index, edges, rng, parallel_edges), _g(g) {} - pair get_target_edge(pair& e) + pair get_target_edge(pair& e, bool parallel_edges) { std::uniform_int_distribution<> sample(0, base_t::_edges.size() - 1); pair et = make_pair(sample(base_t::_rng), false); @@ -530,8 +608,8 @@ public: CorrelatedRewireStrategy(Graph& g, EdgeIndexMap edge_index, vector& edges, CorrProb, BlockDeg blockdeg, - bool, rng_t& rng) - : base_t(g, edge_index, edges, rng), _blockdeg(blockdeg), _g(g) + bool, rng_t& rng, bool parallel_edges) + : base_t(g, edge_index, edges, rng, parallel_edges), _blockdeg(blockdeg), _g(g) { #ifdef HAVE_SPARSEHASH _edges_by_target.set_empty_key(get_null_key()()); @@ -557,7 +635,7 @@ public: } } - pair get_target_edge(pair& e) + pair get_target_edge(pair& e, bool parallel_edge) { if (!is_directed::apply::type::value) { @@ -629,8 +707,9 @@ public: ProbabilisticRewireStrategy(Graph& g, EdgeIndexMap edge_index, vector& edges, CorrProb corr_prob, - BlockDeg blockdeg, bool cache, rng_t& rng) - : base_t(g, edge_index, edges, rng), _g(g), _corr_prob(corr_prob), + BlockDeg blockdeg, bool cache, rng_t& rng, + bool parallel_edges) + : base_t(g, edge_index, edges, rng, parallel_edges), _g(g), _corr_prob(corr_prob), _blockdeg(blockdeg) { #ifdef HAVE_SPARSEHASH @@ -689,7 +768,8 @@ public: return _blockdeg.get_block(v, g); } - pair get_target_edge(pair& e) + pair get_target_edge(pair& e, + bool parallel_edges) { if (!is_directed::apply::type::value) { @@ -710,6 +790,10 @@ public: 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_t_deg = get_deg(target(ep, base_t::_edges, _g), _g); @@ -775,9 +859,10 @@ public: AliasProbabilisticRewireStrategy(Graph& g, EdgeIndexMap edge_index, vector& edges, CorrProb corr_prob, - BlockDeg blockdeg, bool, rng_t& rng) - : base_t(g, edge_index, edges, rng), _g(g), _corr_prob(corr_prob), - _blockdeg(blockdeg) + BlockDeg blockdeg, bool, rng_t& rng, + bool parallel_edges) + : base_t(g, edge_index, edges, rng, parallel_edges), _g(g), + _corr_prob(corr_prob), _blockdeg(blockdeg) { #ifdef HAVE_SPARSEHASH @@ -853,7 +938,6 @@ public: else er = max(_in_edges[items[i]].size(), numeric_limits::min()); - //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: return _blockdeg.get_block(v, g); } - pair get_target_edge(pair& e) + pair get_target_edge(pair& e, + bool parallel_edges) { if (!is_directed::apply::type::value) { @@ -953,6 +1038,9 @@ public: deg_t nt = iter->second->sample(base_t::_rng); + if (_in_edges[nt].empty() && _out_edges[nt].empty()) + return e; // reject + pair ep; std::bernoulli_distribution coin(_in_edges[nt].size() / double(_in_edges[nt].size() + @@ -976,16 +1064,17 @@ public: 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_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_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 pf = get_prob(s_deg, ep_t_deg) + get_prob(ep_s_deg, t_deg); @@ -1144,7 +1233,8 @@ public: TradBlockRewireStrategy(Graph& g, EdgeIndexMap edge_index, vector& 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), _blockdeg(blockdeg), _rng(rng), _sampler(nullptr) @@ -1210,19 +1300,29 @@ public: bool operator()(size_t ei, bool self_loops, bool parallel_edges) { - const pair& deg = _sampler->sample(_rng); + typename graph_traits::vertex_descriptor s, t; + + while (true) + { + const pair& deg = _sampler->sample(_rng); - vector& svs = _vertices[deg.first]; - vector& tvs = _vertices[deg.second]; - std::uniform_int_distribution s_sample(0, svs.size() - 1); - std::uniform_int_distribution t_sample(0, tvs.size() - 1); + vector& svs = _vertices[deg.first]; + vector& tvs = _vertices[deg.second]; - typename graph_traits::vertex_descriptor s, t; - s = svs[s_sample(_rng)]; - t = tvs[t_sample(_rng)]; + if (svs.empty() || tvs.empty()) + continue; + + std::uniform_int_distribution s_sample(0, svs.size() - 1); + std::uniform_int_distribution t_sample(0, tvs.size() - 1); + + s = svs[s_sample(_rng)]; + t = tvs[t_sample(_rng)]; + + break; + } // reject self-loops if not allowed - if(!self_loops && s == t) + if (!self_loops && s == t) return false; // reject parallel edges if not allowed diff --git a/src/graph_tool/generation/__init__.py b/src/graph_tool/generation/__init__.py index cad7420bb2e945fa92a441a77db978298c4ceeab..3aea381e462e8faee436cec3e3b8c4b701c6a84d 100644 --- a/src/graph_tool/generation/__init__.py +++ b/src/graph_tool/generation/__init__.py @@ -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` """ - if not parallel_edges: - p = label_parallel_edges(g) - if p.a.max() != 0: - raise ValueError("Parallel edge detected. Can't rewire " + - "graph without parallel edges if it " + - "already contains parallel edges!") - if not self_loops: - l = label_self_loops(g) - if l.a.max() != 0: - raise ValueError("Self-loop detected. Can't rewire graph " + - "without self-loops if it already contains" + - " self-loops!") + # if not parallel_edges: + # p = label_parallel_edges(g) + # if p.a.max() != 0: + # raise ValueError("Parallel edge detected. Can't rewire " + + # "graph without parallel edges if it " + + # "already contains parallel edges!") + + # if not self_loops: + # l = label_self_loops(g) + # if l.a.max() != 0: + # raise ValueError("Self-loop detected. Can't rewire graph " + + # "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: corr = lambda i, j: vertex_corr(i[1], j[1])