]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // Copyright (C) 2004-2006 The Trustees of Indiana University. |
2 | ||
3 | // Use, modification and distribution is subject to the Boost Software | |
4 | // License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at | |
5 | // http://www.boost.org/LICENSE_1_0.txt) | |
6 | ||
7 | // Authors: Douglas Gregor | |
8 | // Andrew Lumsdaine | |
9 | ||
10 | /** | |
11 | * This header implements four distributed algorithms to compute | |
12 | * the minimum spanning tree (actually, minimum spanning forest) of a | |
13 | * graph. All of the algorithms were implemented as specified in the | |
14 | * paper by Dehne and Gotz: | |
15 | * | |
16 | * Frank Dehne and Silvia Gotz. Practical Parallel Algorithms for Minimum | |
17 | * Spanning Trees. In Symposium on Reliable Distributed Systems, | |
18 | * pages 366--371, 1998. | |
19 | * | |
20 | * There are four algorithm variants implemented. | |
21 | */ | |
22 | ||
23 | #ifndef BOOST_DEHNE_GOTZ_MIN_SPANNING_TREE_HPP | |
24 | #define BOOST_DEHNE_GOTZ_MIN_SPANNING_TREE_HPP | |
25 | ||
26 | #ifndef BOOST_GRAPH_USE_MPI | |
27 | #error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included" | |
28 | #endif | |
29 | ||
30 | #include <boost/graph/graph_traits.hpp> | |
31 | #include <boost/property_map/property_map.hpp> | |
32 | #include <vector> | |
33 | #include <boost/graph/parallel/algorithm.hpp> | |
34 | #include <boost/limits.hpp> | |
35 | #include <utility> | |
36 | #include <boost/pending/disjoint_sets.hpp> | |
37 | #include <boost/pending/indirect_cmp.hpp> | |
38 | #include <boost/property_map/parallel/caching_property_map.hpp> | |
39 | #include <boost/graph/vertex_and_edge_range.hpp> | |
40 | #include <boost/graph/kruskal_min_spanning_tree.hpp> | |
41 | #include <boost/iterator/counting_iterator.hpp> | |
42 | #include <boost/iterator/transform_iterator.hpp> | |
43 | #include <boost/graph/parallel/container_traits.hpp> | |
44 | #include <boost/graph/parallel/detail/untracked_pair.hpp> | |
45 | #include <cmath> | |
46 | ||
47 | namespace boost { namespace graph { namespace distributed { | |
48 | ||
49 | namespace detail { | |
50 | /** | |
51 | * Binary function object type that selects the (edge, weight) pair | |
52 | * with the minimum weight. Used within a Boruvka merge step to select | |
53 | * the candidate edges incident to each supervertex. | |
54 | */ | |
55 | struct smaller_weighted_edge | |
56 | { | |
57 | template<typename Edge, typename Weight> | |
58 | std::pair<Edge, Weight> | |
59 | operator()(const std::pair<Edge, Weight>& x, | |
60 | const std::pair<Edge, Weight>& y) const | |
61 | { return x.second < y.second? x : y; } | |
62 | }; | |
63 | ||
64 | /** | |
65 | * Unary predicate that determines if the source and target vertices | |
66 | * of the given edge have the same representative within a disjoint | |
67 | * sets data structure. Used to indicate when an edge is now a | |
68 | * self-loop because of supervertex merging in Boruvka's algorithm. | |
69 | */ | |
70 | template<typename DisjointSets, typename Graph> | |
71 | class do_has_same_supervertex | |
72 | { | |
73 | public: | |
74 | typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor; | |
75 | ||
76 | do_has_same_supervertex(DisjointSets& dset, const Graph& g) | |
77 | : dset(dset), g(g) { } | |
78 | ||
79 | bool operator()(edge_descriptor e) | |
80 | { return dset.find_set(source(e, g)) == dset.find_set(target(e, g)); } | |
81 | ||
82 | private: | |
83 | DisjointSets& dset; | |
84 | const Graph& g; | |
85 | }; | |
86 | ||
87 | /** | |
88 | * Build a @ref do_has_same_supervertex object. | |
89 | */ | |
90 | template<typename DisjointSets, typename Graph> | |
91 | inline do_has_same_supervertex<DisjointSets, Graph> | |
92 | has_same_supervertex(DisjointSets& dset, const Graph& g) | |
93 | { return do_has_same_supervertex<DisjointSets, Graph>(dset, g); } | |
94 | ||
95 | /** \brief A single distributed Boruvka merge step. | |
96 | * | |
97 | * A distributed Boruvka merge step involves computing (globally) | |
98 | * the minimum weight edges incident on each supervertex and then | |
99 | * merging supervertices along these edges. Once supervertices are | |
100 | * merged, self-loops are eliminated. | |
101 | * | |
102 | * The set of parameters passed to this algorithm is large, and | |
103 | * considering this algorithm in isolation there are several | |
104 | * redundancies. However, the more asymptotically efficient | |
105 | * distributed MSF algorithms require mixing Boruvka steps with the | |
106 | * merging of local MSFs (implemented in | |
107 | * merge_local_minimum_spanning_trees_step): the interaction of the | |
108 | * two algorithms mandates the addition of these parameters. | |
109 | * | |
110 | * \param pg The process group over which communication should be | |
111 | * performed. Within the distributed Boruvka algorithm, this will be | |
112 | * equivalent to \code process_group(g); however, in the context of | |
113 | * the mixed MSF algorithms, the process group @p pg will be a | |
114 | * (non-strict) process subgroup of \code process_group(g). | |
115 | * | |
116 | * \param g The underlying graph on which the MSF is being | |
117 | * computed. The type of @p g must model DistributedGraph, but there | |
118 | * are no other requirements because the edge and (super)vertex | |
119 | * lists are passed separately. | |
120 | * | |
121 | * \param weight_map Property map containing the weights of each | |
122 | * edge. The type of this property map must model | |
123 | * ReadablePropertyMap and must support caching. | |
124 | * | |
125 | * \param out An output iterator that will be written with the set | |
126 | * of edges selected to build the MSF. Every process within the | |
127 | * process group @p pg will receive all edges in the MSF. | |
128 | * | |
129 | * \param dset Disjoint sets data structure mapping from vertices in | |
130 | * the graph @p g to their representative supervertex. | |
131 | * | |
132 | * \param supervertex_map Mapping from supervertex descriptors to | |
133 | * indices. | |
134 | * | |
135 | * \param supervertices A vector containing all of the | |
136 | * supervertices. Will be modified to include only the remaining | |
137 | * supervertices after merging occurs. | |
138 | * | |
139 | * \param edge_list The list of edges that remain in the graph. This | |
140 | * list will be pruned to remove self-loops once MSF edges have been | |
141 | * found. | |
142 | */ | |
143 | template<typename ProcessGroup, typename Graph, typename WeightMap, | |
144 | typename OutputIterator, typename RankMap, typename ParentMap, | |
145 | typename SupervertexMap, typename Vertex, typename EdgeList> | |
146 | OutputIterator | |
147 | boruvka_merge_step(ProcessGroup pg, const Graph& g, WeightMap weight_map, | |
148 | OutputIterator out, | |
149 | disjoint_sets<RankMap, ParentMap>& dset, | |
150 | SupervertexMap supervertex_map, | |
151 | std::vector<Vertex>& supervertices, | |
152 | EdgeList& edge_list) | |
153 | { | |
154 | typedef typename graph_traits<Graph>::vertex_descriptor | |
155 | vertex_descriptor; | |
156 | typedef typename graph_traits<Graph>::vertices_size_type | |
157 | vertices_size_type; | |
158 | typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor; | |
159 | typedef typename EdgeList::iterator edge_iterator; | |
160 | typedef typename property_traits<WeightMap>::value_type | |
161 | weight_type; | |
162 | typedef boost::parallel::detail::untracked_pair<edge_descriptor, | |
163 | weight_type> w_edge; | |
164 | typedef typename property_traits<SupervertexMap>::value_type | |
165 | supervertex_index; | |
166 | ||
167 | smaller_weighted_edge min_edge; | |
168 | weight_type inf = (std::numeric_limits<weight_type>::max)(); | |
169 | ||
170 | // Renumber the supervertices | |
171 | for (std::size_t i = 0; i < supervertices.size(); ++i) | |
172 | put(supervertex_map, supervertices[i], i); | |
173 | ||
174 | // BSP-B1: Find local minimum-weight edges for each supervertex | |
175 | std::vector<w_edge> candidate_edges(supervertices.size(), | |
176 | w_edge(edge_descriptor(), inf)); | |
177 | for (edge_iterator ei = edge_list.begin(); ei != edge_list.end(); ++ei) { | |
178 | weight_type w = get(weight_map, *ei); | |
179 | supervertex_index u = | |
180 | get(supervertex_map, dset.find_set(source(*ei, g))); | |
181 | supervertex_index v = | |
182 | get(supervertex_map, dset.find_set(target(*ei, g))); | |
183 | ||
184 | if (u != v) { | |
185 | candidate_edges[u] = min_edge(candidate_edges[u], w_edge(*ei, w)); | |
186 | candidate_edges[v] = min_edge(candidate_edges[v], w_edge(*ei, w)); | |
187 | } | |
188 | } | |
189 | ||
190 | // BSP-B2 (a): Compute global minimum edges for each supervertex | |
191 | all_reduce(pg, | |
192 | &candidate_edges[0], | |
193 | &candidate_edges[0] + candidate_edges.size(), | |
194 | &candidate_edges[0], min_edge); | |
195 | ||
196 | // BSP-B2 (b): Use the edges to compute sequentially the new | |
197 | // connected components and emit the edges. | |
198 | for (vertices_size_type i = 0; i < candidate_edges.size(); ++i) { | |
199 | if (candidate_edges[i].second != inf) { | |
200 | edge_descriptor e = candidate_edges[i].first; | |
201 | vertex_descriptor u = dset.find_set(source(e, g)); | |
202 | vertex_descriptor v = dset.find_set(target(e, g)); | |
203 | if (u != v) { | |
204 | // Emit the edge, but cache the weight so everyone knows it | |
205 | cache(weight_map, e, candidate_edges[i].second); | |
206 | *out++ = e; | |
207 | ||
208 | // Link the two supervertices | |
209 | dset.link(u, v); | |
210 | ||
211 | // Whichever vertex was reparented will be removed from the | |
212 | // list of supervertices. | |
213 | vertex_descriptor victim = u; | |
214 | if (dset.find_set(u) == u) victim = v; | |
215 | supervertices[get(supervertex_map, victim)] = | |
216 | graph_traits<Graph>::null_vertex(); | |
217 | } | |
218 | } | |
219 | } | |
220 | ||
221 | // BSP-B3: Eliminate self-loops | |
222 | edge_list.erase(std::remove_if(edge_list.begin(), edge_list.end(), | |
223 | has_same_supervertex(dset, g)), | |
224 | edge_list.end()); | |
225 | ||
226 | // TBD: might also eliminate multiple edges between supervertices | |
227 | // when the edges do not have the best weight, but this is not | |
228 | // strictly necessary. | |
229 | ||
230 | // Eliminate supervertices that have been absorbed | |
231 | supervertices.erase(std::remove(supervertices.begin(), | |
232 | supervertices.end(), | |
233 | graph_traits<Graph>::null_vertex()), | |
234 | supervertices.end()); | |
235 | ||
236 | return out; | |
237 | } | |
238 | ||
239 | /** | |
240 | * An edge descriptor adaptor that reroutes the source and target | |
241 | * edges to different vertices, but retains the original edge | |
242 | * descriptor for, e.g., property maps. This is used when we want to | |
243 | * turn a set of edges in the overall graph into a set of edges | |
244 | * between supervertices. | |
245 | */ | |
246 | template<typename Graph> | |
247 | struct supervertex_edge_descriptor | |
248 | { | |
249 | typedef supervertex_edge_descriptor self_type; | |
250 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; | |
251 | typedef typename graph_traits<Graph>::edge_descriptor Edge; | |
252 | ||
253 | Vertex source; | |
254 | Vertex target; | |
255 | Edge e; | |
256 | ||
257 | operator Edge() const { return e; } | |
258 | ||
259 | friend inline bool operator==(const self_type& x, const self_type& y) | |
260 | { return x.e == y.e; } | |
261 | ||
262 | friend inline bool operator!=(const self_type& x, const self_type& y) | |
263 | { return x.e != y.e; } | |
264 | }; | |
265 | ||
266 | template<typename Graph> | |
267 | inline typename supervertex_edge_descriptor<Graph>::Vertex | |
268 | source(supervertex_edge_descriptor<Graph> se, const Graph&) | |
269 | { return se.source; } | |
270 | ||
271 | template<typename Graph> | |
272 | inline typename supervertex_edge_descriptor<Graph>::Vertex | |
273 | target(supervertex_edge_descriptor<Graph> se, const Graph&) | |
274 | { return se.target; } | |
275 | ||
276 | /** | |
277 | * Build a supervertex edge descriptor from a normal edge descriptor | |
278 | * using the given disjoint sets data structure to identify | |
279 | * supervertex representatives. | |
280 | */ | |
281 | template<typename Graph, typename DisjointSets> | |
282 | struct build_supervertex_edge_descriptor | |
283 | { | |
284 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; | |
285 | typedef typename graph_traits<Graph>::edge_descriptor Edge; | |
286 | ||
287 | typedef Edge argument_type; | |
288 | typedef supervertex_edge_descriptor<Graph> result_type; | |
289 | ||
290 | build_supervertex_edge_descriptor() : g(0), dsets(0) { } | |
291 | ||
292 | build_supervertex_edge_descriptor(const Graph& g, DisjointSets& dsets) | |
293 | : g(&g), dsets(&dsets) { } | |
294 | ||
295 | result_type operator()(argument_type e) const | |
296 | { | |
297 | result_type result; | |
298 | result.source = dsets->find_set(source(e, *g)); | |
299 | result.target = dsets->find_set(target(e, *g)); | |
300 | result.e = e; | |
301 | return result; | |
302 | } | |
303 | ||
304 | private: | |
305 | const Graph* g; | |
306 | DisjointSets* dsets; | |
307 | }; | |
308 | ||
309 | template<typename Graph, typename DisjointSets> | |
310 | inline build_supervertex_edge_descriptor<Graph, DisjointSets> | |
311 | make_supervertex_edge_descriptor(const Graph& g, DisjointSets& dsets) | |
312 | { return build_supervertex_edge_descriptor<Graph, DisjointSets>(g, dsets); } | |
313 | ||
314 | template<typename T> | |
315 | struct identity_function | |
316 | { | |
317 | typedef T argument_type; | |
318 | typedef T result_type; | |
319 | ||
320 | result_type operator()(argument_type x) const { return x; } | |
321 | }; | |
322 | ||
323 | template<typename Graph, typename DisjointSets, typename EdgeMapper> | |
324 | class is_not_msf_edge | |
325 | { | |
326 | typedef typename graph_traits<Graph>::vertex_descriptor Vertex; | |
327 | typedef typename graph_traits<Graph>::edge_descriptor Edge; | |
328 | ||
329 | public: | |
330 | is_not_msf_edge(const Graph& g, DisjointSets dset, EdgeMapper edge_mapper) | |
331 | : g(g), dset(dset), edge_mapper(edge_mapper) { } | |
332 | ||
333 | bool operator()(Edge e) | |
334 | { | |
335 | Vertex u = dset.find_set(source(edge_mapper(e), g)); | |
336 | Vertex v = dset.find_set(target(edge_mapper(e), g)); | |
337 | if (u == v) return true; | |
338 | else { | |
339 | dset.link(u, v); | |
340 | return false; | |
341 | } | |
342 | } | |
343 | ||
344 | private: | |
345 | const Graph& g; | |
346 | DisjointSets dset; | |
347 | EdgeMapper edge_mapper; | |
348 | }; | |
349 | ||
350 | template<typename Graph, typename ForwardIterator, typename EdgeList, | |
351 | typename EdgeMapper, typename RankMap, typename ParentMap> | |
352 | void | |
353 | sorted_mutating_kruskal(const Graph& g, | |
354 | ForwardIterator first_vertex, | |
355 | ForwardIterator last_vertex, | |
356 | EdgeList& edge_list, EdgeMapper edge_mapper, | |
357 | RankMap rank_map, ParentMap parent_map) | |
358 | { | |
359 | typedef disjoint_sets<RankMap, ParentMap> DisjointSets; | |
360 | ||
361 | // Build and initialize disjoint-sets data structure | |
362 | DisjointSets dset(rank_map, parent_map); | |
363 | for (ForwardIterator v = first_vertex; v != last_vertex; ++v) | |
364 | dset.make_set(*v); | |
365 | ||
366 | is_not_msf_edge<Graph, DisjointSets, EdgeMapper> | |
367 | remove_non_msf_edges(g, dset, edge_mapper); | |
368 | edge_list.erase(std::remove_if(edge_list.begin(), edge_list.end(), | |
369 | remove_non_msf_edges), | |
370 | edge_list.end()); | |
371 | } | |
372 | ||
373 | /** | |
374 | * Merge local minimum spanning forests from p processes into | |
375 | * minimum spanning forests on p/D processes (where D is the tree | |
376 | * factor, currently fixed at 3), eliminating unnecessary edges in | |
377 | * the process. | |
378 | * | |
379 | * As with @ref boruvka_merge_step, this routine has many | |
380 | * parameters, not all of which make sense within the limited | |
381 | * context of this routine. The parameters are required for the | |
382 | * Boruvka and local MSF merging steps to interoperate. | |
383 | * | |
384 | * \param pg The process group on which local minimum spanning | |
385 | * forests should be merged. The top (D-1)p/D processes will be | |
386 | * eliminated, and a new process subgroup containing p/D processors | |
387 | * will be returned. The value D is a constant factor that is | |
388 | * currently fixed to 3. | |
389 | * | |
390 | * \param g The underlying graph whose MSF is being computed. It must model | |
391 | * the DistributedGraph concept. | |
392 | * | |
393 | * \param first_vertex Iterator to the first vertex in the graph | |
394 | * that should be considered. While the local MSF merging algorithm | |
395 | * typically operates on the entire vertex set, within the hybrid | |
396 | * distributed MSF algorithms this will refer to the first | |
397 | * supervertex. | |
398 | * | |
399 | * \param last_vertex The past-the-end iterator for the vertex list. | |
400 | * | |
401 | * \param edge_list The list of local edges that will be | |
402 | * considered. For the p/D processes that remain, this list will | |
403 | * contain edges in the MSF known to the vertex after other | |
404 | * processes' edge lists have been merged. The edge list must be | |
405 | * sorted in order of increasing weight. | |
406 | * | |
407 | * \param weight Property map containing the weights of each | |
408 | * edge. The type of this property map must model | |
409 | * ReadablePropertyMap and must support caching. | |
410 | * | |
411 | * \param global_index Mapping from vertex descriptors to a global | |
412 | * index. The type must model ReadablePropertyMap. | |
413 | * | |
414 | * \param edge_mapper A function object that can remap edge descriptors | |
415 | * in the edge list to any alternative edge descriptor. This | |
416 | * function object will be the identity function when a pure merging | |
417 | * of local MSFs is required, but may be a mapping to a supervertex | |
418 | * edge when the local MSF merging occurs on a supervertex | |
419 | * graph. This function object saves us the trouble of having to | |
420 | * build a supervertex graph adaptor. | |
421 | * | |
422 | * \param already_local_msf True when the edge list already | |
423 | * constitutes a local MSF. If false, Kruskal's algorithm will first | |
424 | * be applied to the local edge list to select MSF edges. | |
425 | * | |
426 | * \returns The process subgroup containing the remaining p/D | |
427 | * processes. If the size of this process group is greater than one, | |
428 | * the MSF edges contained in the edge list do not constitute an MSF | |
429 | * for the entire graph. | |
430 | */ | |
431 | template<typename ProcessGroup, typename Graph, typename ForwardIterator, | |
432 | typename EdgeList, typename WeightMap, typename GlobalIndexMap, | |
433 | typename EdgeMapper> | |
434 | ProcessGroup | |
435 | merge_local_minimum_spanning_trees_step(ProcessGroup pg, | |
436 | const Graph& g, | |
437 | ForwardIterator first_vertex, | |
438 | ForwardIterator last_vertex, | |
439 | EdgeList& edge_list, | |
440 | WeightMap weight, | |
441 | GlobalIndexMap global_index, | |
442 | EdgeMapper edge_mapper, | |
443 | bool already_local_msf) | |
444 | { | |
445 | typedef typename ProcessGroup::process_id_type process_id_type; | |
446 | typedef typename EdgeList::value_type edge_descriptor; | |
447 | typedef typename property_traits<WeightMap>::value_type weight_type; | |
448 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
449 | ||
450 | // The tree factor, often called "D" | |
451 | process_id_type const tree_factor = 3; | |
452 | process_id_type num_procs = num_processes(pg); | |
453 | process_id_type id = process_id(pg); | |
454 | process_id_type procs_left = (num_procs + tree_factor - 1) / tree_factor; | |
455 | std::size_t n = std::size_t(last_vertex - first_vertex); | |
456 | ||
457 | if (!already_local_msf) { | |
458 | // Compute local minimum spanning forest. We only care about the | |
459 | // edges in the MSF, because only edges in the local MSF can be in | |
460 | // the global MSF. | |
461 | std::vector<std::size_t> ranks(n); | |
462 | std::vector<vertex_descriptor> parents(n); | |
463 | detail::sorted_mutating_kruskal | |
464 | (g, first_vertex, last_vertex, | |
465 | edge_list, edge_mapper, | |
466 | make_iterator_property_map(ranks.begin(), global_index), | |
467 | make_iterator_property_map(parents.begin(), global_index)); | |
468 | } | |
469 | ||
470 | typedef std::pair<edge_descriptor, weight_type> w_edge; | |
471 | ||
472 | // Order edges based on their weights. | |
473 | indirect_cmp<WeightMap, std::less<weight_type> > cmp_edge_weight(weight); | |
474 | ||
475 | if (id < procs_left) { | |
476 | // The p/D processes that remain will receive local MSF edges from | |
477 | // D-1 other processes. | |
478 | synchronize(pg); | |
479 | for (process_id_type from_id = procs_left + id; from_id < num_procs; | |
480 | from_id += procs_left) { | |
481 | std::size_t num_incoming_edges; | |
482 | receive(pg, from_id, 0, num_incoming_edges); | |
483 | if (num_incoming_edges > 0) { | |
484 | std::vector<w_edge> incoming_edges(num_incoming_edges); | |
485 | receive(pg, from_id, 1, &incoming_edges[0], num_incoming_edges); | |
486 | ||
487 | edge_list.reserve(edge_list.size() + num_incoming_edges); | |
488 | for (std::size_t i = 0; i < num_incoming_edges; ++i) { | |
489 | cache(weight, incoming_edges[i].first, incoming_edges[i].second); | |
490 | edge_list.push_back(incoming_edges[i].first); | |
491 | } | |
492 | std::inplace_merge(edge_list.begin(), | |
493 | edge_list.end() - num_incoming_edges, | |
494 | edge_list.end(), | |
495 | cmp_edge_weight); | |
496 | } | |
497 | } | |
498 | ||
499 | // Compute the local MSF from union of the edges in the MSFs of | |
500 | // all children. | |
501 | std::vector<std::size_t> ranks(n); | |
502 | std::vector<vertex_descriptor> parents(n); | |
503 | detail::sorted_mutating_kruskal | |
504 | (g, first_vertex, last_vertex, | |
505 | edge_list, edge_mapper, | |
506 | make_iterator_property_map(ranks.begin(), global_index), | |
507 | make_iterator_property_map(parents.begin(), global_index)); | |
508 | } else { | |
509 | // The (D-1)p/D processes that are dropping out of further | |
510 | // computations merely send their MSF edges to their parent | |
511 | // process in the process tree. | |
512 | send(pg, id % procs_left, 0, edge_list.size()); | |
513 | if (edge_list.size() > 0) { | |
514 | std::vector<w_edge> outgoing_edges; | |
515 | outgoing_edges.reserve(edge_list.size()); | |
516 | for (std::size_t i = 0; i < edge_list.size(); ++i) { | |
517 | outgoing_edges.push_back(std::make_pair(edge_list[i], | |
518 | get(weight, edge_list[i]))); | |
519 | } | |
520 | send(pg, id % procs_left, 1, &outgoing_edges[0], | |
521 | outgoing_edges.size()); | |
522 | } | |
523 | synchronize(pg); | |
524 | } | |
525 | ||
526 | // Return a process subgroup containing the p/D parent processes | |
527 | return process_subgroup(pg, | |
528 | make_counting_iterator(process_id_type(0)), | |
529 | make_counting_iterator(procs_left)); | |
530 | } | |
531 | } // end namespace detail | |
532 | ||
533 | // --------------------------------------------------------------------- | |
534 | // Dense Boruvka MSF algorithm | |
535 | // --------------------------------------------------------------------- | |
536 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
537 | typename VertexIndexMap, typename RankMap, typename ParentMap, | |
538 | typename SupervertexMap> | |
539 | OutputIterator | |
540 | dense_boruvka_minimum_spanning_tree(const Graph& g, WeightMap weight_map, | |
541 | OutputIterator out, | |
542 | VertexIndexMap index_map, | |
543 | RankMap rank_map, ParentMap parent_map, | |
544 | SupervertexMap supervertex_map) | |
545 | { | |
546 | using boost::graph::parallel::process_group; | |
547 | ||
548 | typedef typename graph_traits<Graph>::traversal_category traversal_category; | |
549 | ||
550 | BOOST_STATIC_ASSERT((is_convertible<traversal_category*, | |
551 | vertex_list_graph_tag*>::value)); | |
552 | ||
553 | typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type; | |
554 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
555 | typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator; | |
556 | typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor; | |
557 | ||
558 | // Don't throw away cached edge weights | |
559 | weight_map.set_max_ghost_cells(0); | |
560 | ||
561 | // Initialize the disjoint sets structures | |
562 | disjoint_sets<RankMap, ParentMap> dset(rank_map, parent_map); | |
563 | vertex_iterator vi, vi_end; | |
564 | for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) | |
565 | dset.make_set(*vi); | |
566 | ||
567 | std::vector<vertex_descriptor> supervertices; | |
568 | supervertices.assign(vertices(g).first, vertices(g).second); | |
569 | ||
570 | // Use Kruskal's algorithm to find the minimum spanning forest | |
571 | // considering only the local edges. The resulting edges are not | |
572 | // necessarily going to be in the final minimum spanning | |
573 | // forest. However, any edge not part of the local MSF cannot be a | |
574 | // part of the global MSF, so we should have eliminated some edges | |
575 | // from consideration. | |
576 | std::vector<edge_descriptor> edge_list; | |
577 | kruskal_minimum_spanning_tree | |
578 | (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, | |
579 | edges(g).first, edges(g).second), | |
580 | std::back_inserter(edge_list), | |
581 | boost::weight_map(weight_map). | |
582 | vertex_index_map(index_map)); | |
583 | ||
584 | // While the number of supervertices is decreasing, keep executing | |
585 | // Boruvka steps to identify additional MSF edges. This loop will | |
586 | // execute log |V| times. | |
587 | vertices_size_type old_num_supervertices; | |
588 | do { | |
589 | old_num_supervertices = supervertices.size(); | |
590 | out = detail::boruvka_merge_step(process_group(g), g, | |
591 | weight_map, out, | |
592 | dset, supervertex_map, supervertices, | |
593 | edge_list); | |
594 | } while (supervertices.size() < old_num_supervertices); | |
595 | ||
596 | return out; | |
597 | } | |
598 | ||
599 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
600 | typename VertexIndex> | |
601 | OutputIterator | |
602 | dense_boruvka_minimum_spanning_tree(const Graph& g, WeightMap weight_map, | |
603 | OutputIterator out, VertexIndex i_map) | |
604 | { | |
605 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
606 | ||
607 | std::vector<std::size_t> ranks(num_vertices(g)); | |
608 | std::vector<vertex_descriptor> parents(num_vertices(g)); | |
609 | std::vector<std::size_t> supervertices(num_vertices(g)); | |
610 | ||
611 | return dense_boruvka_minimum_spanning_tree | |
612 | (g, weight_map, out, i_map, | |
613 | make_iterator_property_map(ranks.begin(), i_map), | |
614 | make_iterator_property_map(parents.begin(), i_map), | |
615 | make_iterator_property_map(supervertices.begin(), i_map)); | |
616 | } | |
617 | ||
618 | template<typename Graph, typename WeightMap, typename OutputIterator> | |
619 | OutputIterator | |
620 | dense_boruvka_minimum_spanning_tree(const Graph& g, WeightMap weight_map, | |
621 | OutputIterator out) | |
622 | { | |
623 | return dense_boruvka_minimum_spanning_tree(g, weight_map, out, | |
624 | get(vertex_index, g)); | |
625 | } | |
626 | ||
627 | // --------------------------------------------------------------------- | |
628 | // Merge local MSFs MSF algorithm | |
629 | // --------------------------------------------------------------------- | |
630 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
631 | typename GlobalIndexMap> | |
632 | OutputIterator | |
633 | merge_local_minimum_spanning_trees(const Graph& g, WeightMap weight, | |
634 | OutputIterator out, | |
635 | GlobalIndexMap global_index) | |
636 | { | |
637 | using boost::graph::parallel::process_group_type; | |
638 | using boost::graph::parallel::process_group; | |
639 | ||
640 | typedef typename graph_traits<Graph>::traversal_category traversal_category; | |
641 | ||
642 | BOOST_STATIC_ASSERT((is_convertible<traversal_category*, | |
643 | vertex_list_graph_tag*>::value)); | |
644 | ||
645 | typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor; | |
646 | ||
647 | // Don't throw away cached edge weights | |
648 | weight.set_max_ghost_cells(0); | |
649 | ||
650 | // Compute the initial local minimum spanning forests | |
651 | std::vector<edge_descriptor> edge_list; | |
652 | kruskal_minimum_spanning_tree | |
653 | (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, | |
654 | edges(g).first, edges(g).second), | |
655 | std::back_inserter(edge_list), | |
656 | boost::weight_map(weight).vertex_index_map(global_index)); | |
657 | ||
658 | // Merge the local MSFs from p processes into p/D processes, | |
659 | // reducing the number of processes in each step. Continue looping | |
660 | // until either (a) the current process drops out or (b) only one | |
661 | // process remains in the group. This loop will execute log_D p | |
662 | // times. | |
663 | typename process_group_type<Graph>::type pg = process_group(g); | |
664 | while (pg && num_processes(pg) > 1) { | |
665 | pg = detail::merge_local_minimum_spanning_trees_step | |
666 | (pg, g, vertices(g).first, vertices(g).second, | |
667 | edge_list, weight, global_index, | |
668 | detail::identity_function<edge_descriptor>(), true); | |
669 | } | |
670 | ||
671 | // Only process 0 has the entire edge list, so emit it to the output | |
672 | // iterator. | |
673 | if (pg && process_id(pg) == 0) { | |
674 | out = std::copy(edge_list.begin(), edge_list.end(), out); | |
675 | } | |
676 | ||
677 | synchronize(process_group(g)); | |
678 | return out; | |
679 | } | |
680 | ||
681 | template<typename Graph, typename WeightMap, typename OutputIterator> | |
682 | inline OutputIterator | |
683 | merge_local_minimum_spanning_trees(const Graph& g, WeightMap weight, | |
684 | OutputIterator out) | |
685 | { | |
686 | return merge_local_minimum_spanning_trees(g, weight, out, | |
687 | get(vertex_index, g)); | |
688 | } | |
689 | ||
690 | // --------------------------------------------------------------------- | |
691 | // Boruvka-then-merge MSF algorithm | |
692 | // --------------------------------------------------------------------- | |
693 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
694 | typename GlobalIndexMap, typename RankMap, typename ParentMap, | |
695 | typename SupervertexMap> | |
696 | OutputIterator | |
697 | boruvka_then_merge(const Graph& g, WeightMap weight, OutputIterator out, | |
698 | GlobalIndexMap index, RankMap rank_map, | |
699 | ParentMap parent_map, SupervertexMap supervertex_map) | |
700 | { | |
701 | using std::log; | |
702 | using boost::graph::parallel::process_group_type; | |
703 | using boost::graph::parallel::process_group; | |
704 | ||
705 | typedef typename graph_traits<Graph>::traversal_category traversal_category; | |
706 | ||
707 | BOOST_STATIC_ASSERT((is_convertible<traversal_category*, | |
708 | vertex_list_graph_tag*>::value)); | |
709 | ||
710 | typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type; | |
711 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
712 | typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator; | |
713 | typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor; | |
714 | ||
715 | // Don't throw away cached edge weights | |
716 | weight.set_max_ghost_cells(0); | |
717 | ||
718 | // Compute the initial local minimum spanning forests | |
719 | std::vector<edge_descriptor> edge_list; | |
720 | kruskal_minimum_spanning_tree | |
721 | (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, | |
722 | edges(g).first, edges(g).second), | |
723 | std::back_inserter(edge_list), | |
724 | boost::weight_map(weight). | |
725 | vertex_index_map(index)); | |
726 | ||
727 | // Initialize the disjoint sets structures for Boruvka steps | |
728 | disjoint_sets<RankMap, ParentMap> dset(rank_map, parent_map); | |
729 | vertex_iterator vi, vi_end; | |
730 | for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) | |
731 | dset.make_set(*vi); | |
732 | ||
733 | // Construct the initial set of supervertices (all vertices) | |
734 | std::vector<vertex_descriptor> supervertices; | |
735 | supervertices.assign(vertices(g).first, vertices(g).second); | |
736 | ||
737 | // Continue performing Boruvka merge steps until the number of | |
738 | // supervertices reaches |V| / (log_D p)^2. | |
739 | const std::size_t tree_factor = 3; // TBD: same as above! should be param | |
740 | double log_d_p = log((double)num_processes(process_group(g))) | |
741 | / log((double)tree_factor); | |
742 | vertices_size_type target_supervertices = | |
743 | vertices_size_type(num_vertices(g) / (log_d_p * log_d_p)); | |
744 | vertices_size_type old_num_supervertices; | |
745 | while (supervertices.size() > target_supervertices) { | |
746 | old_num_supervertices = supervertices.size(); | |
747 | out = detail::boruvka_merge_step(process_group(g), g, | |
748 | weight, out, dset, | |
749 | supervertex_map, supervertices, | |
750 | edge_list); | |
751 | if (supervertices.size() == old_num_supervertices) | |
752 | return out; | |
753 | } | |
754 | ||
755 | // Renumber the supervertices | |
756 | for (std::size_t i = 0; i < supervertices.size(); ++i) | |
757 | put(supervertex_map, supervertices[i], i); | |
758 | ||
759 | // Merge local MSFs on the supervertices. (D-1)p/D processors drop | |
760 | // out each iteration, so this loop executes log_D p times. | |
761 | typename process_group_type<Graph>::type pg = process_group(g); | |
762 | bool have_msf = false; | |
763 | while (pg && num_processes(pg) > 1) { | |
764 | pg = detail::merge_local_minimum_spanning_trees_step | |
765 | (pg, g, supervertices.begin(), supervertices.end(), | |
766 | edge_list, weight, supervertex_map, | |
767 | detail::make_supervertex_edge_descriptor(g, dset), | |
768 | have_msf); | |
769 | have_msf = true; | |
770 | } | |
771 | ||
772 | // Only process 0 has the complete list of _supervertex_ MST edges, | |
773 | // so emit those to the output iterator. This is not the complete | |
774 | // list of edges in the MSF, however: the Boruvka steps in the | |
775 | // beginning of the algorithm emitted any edges used to merge | |
776 | // supervertices. | |
777 | if (pg && process_id(pg) == 0) | |
778 | out = std::copy(edge_list.begin(), edge_list.end(), out); | |
779 | ||
780 | synchronize(process_group(g)); | |
781 | return out; | |
782 | } | |
783 | ||
784 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
785 | typename GlobalIndexMap> | |
786 | inline OutputIterator | |
787 | boruvka_then_merge(const Graph& g, WeightMap weight, OutputIterator out, | |
788 | GlobalIndexMap index) | |
789 | { | |
790 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
791 | typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type; | |
792 | std::vector<vertices_size_type> ranks(num_vertices(g)); | |
793 | std::vector<vertex_descriptor> parents(num_vertices(g)); | |
794 | std::vector<vertices_size_type> supervertex_indices(num_vertices(g)); | |
795 | ||
796 | return boruvka_then_merge | |
797 | (g, weight, out, index, | |
798 | make_iterator_property_map(ranks.begin(), index), | |
799 | make_iterator_property_map(parents.begin(), index), | |
800 | make_iterator_property_map(supervertex_indices.begin(), index)); | |
801 | } | |
802 | ||
803 | template<typename Graph, typename WeightMap, typename OutputIterator> | |
804 | inline OutputIterator | |
805 | boruvka_then_merge(const Graph& g, WeightMap weight, OutputIterator out) | |
806 | { return boruvka_then_merge(g, weight, out, get(vertex_index, g)); } | |
807 | ||
808 | // --------------------------------------------------------------------- | |
809 | // Boruvka-mixed-merge MSF algorithm | |
810 | // --------------------------------------------------------------------- | |
811 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
812 | typename GlobalIndexMap, typename RankMap, typename ParentMap, | |
813 | typename SupervertexMap> | |
814 | OutputIterator | |
815 | boruvka_mixed_merge(const Graph& g, WeightMap weight, OutputIterator out, | |
816 | GlobalIndexMap index, RankMap rank_map, | |
817 | ParentMap parent_map, SupervertexMap supervertex_map) | |
818 | { | |
819 | using boost::graph::parallel::process_group_type; | |
820 | using boost::graph::parallel::process_group; | |
821 | ||
822 | typedef typename graph_traits<Graph>::traversal_category traversal_category; | |
823 | ||
824 | BOOST_STATIC_ASSERT((is_convertible<traversal_category*, | |
825 | vertex_list_graph_tag*>::value)); | |
826 | ||
827 | typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type; | |
828 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
829 | typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator; | |
830 | typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor; | |
831 | ||
832 | // Don't throw away cached edge weights | |
833 | weight.set_max_ghost_cells(0); | |
834 | ||
835 | // Initialize the disjoint sets structures for Boruvka steps | |
836 | disjoint_sets<RankMap, ParentMap> dset(rank_map, parent_map); | |
837 | vertex_iterator vi, vi_end; | |
838 | for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) | |
839 | dset.make_set(*vi); | |
840 | ||
841 | // Construct the initial set of supervertices (all vertices) | |
842 | std::vector<vertex_descriptor> supervertices; | |
843 | supervertices.assign(vertices(g).first, vertices(g).second); | |
844 | ||
845 | // Compute the initial local minimum spanning forests | |
846 | std::vector<edge_descriptor> edge_list; | |
847 | kruskal_minimum_spanning_tree | |
848 | (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, | |
849 | edges(g).first, edges(g).second), | |
850 | std::back_inserter(edge_list), | |
851 | boost::weight_map(weight). | |
852 | vertex_index_map(index)); | |
853 | ||
854 | if (num_processes(process_group(g)) == 1) { | |
855 | return std::copy(edge_list.begin(), edge_list.end(), out); | |
856 | } | |
857 | ||
858 | // Like the merging local MSFs algorithm and the Boruvka-then-merge | |
859 | // algorithm, each iteration of this loop reduces the number of | |
860 | // processes by a constant factor D, and therefore we require log_D | |
861 | // p iterations. Note also that the number of edges in the edge list | |
862 | // decreases geometrically, giving us an efficient distributed MSF | |
863 | // algorithm. | |
864 | typename process_group_type<Graph>::type pg = process_group(g); | |
865 | vertices_size_type old_num_supervertices; | |
866 | while (pg && num_processes(pg) > 1) { | |
867 | // A single Boruvka step. If this doesn't change anything, we're done | |
868 | old_num_supervertices = supervertices.size(); | |
869 | out = detail::boruvka_merge_step(pg, g, weight, out, dset, | |
870 | supervertex_map, supervertices, | |
871 | edge_list); | |
872 | if (old_num_supervertices == supervertices.size()) { | |
873 | edge_list.clear(); | |
874 | break; | |
875 | } | |
876 | ||
877 | // Renumber the supervertices | |
878 | for (std::size_t i = 0; i < supervertices.size(); ++i) | |
879 | put(supervertex_map, supervertices[i], i); | |
880 | ||
881 | // A single merging of local MSTs, which reduces the number of | |
882 | // processes we're using by a constant factor D. | |
883 | pg = detail::merge_local_minimum_spanning_trees_step | |
884 | (pg, g, supervertices.begin(), supervertices.end(), | |
885 | edge_list, weight, supervertex_map, | |
886 | detail::make_supervertex_edge_descriptor(g, dset), | |
887 | true); | |
888 | ||
889 | } | |
890 | ||
891 | // Only process 0 has the complete edge list, so emit it for the | |
892 | // user. Note that list edge list only contains the MSF edges in the | |
893 | // final supervertex graph: all of the other edges were used to | |
894 | // merge supervertices and have been emitted by the Boruvka steps, | |
895 | // although only process 0 has received the complete set. | |
896 | if (pg && process_id(pg) == 0) | |
897 | out = std::copy(edge_list.begin(), edge_list.end(), out); | |
898 | ||
899 | synchronize(process_group(g)); | |
900 | return out; | |
901 | } | |
902 | ||
903 | template<typename Graph, typename WeightMap, typename OutputIterator, | |
904 | typename GlobalIndexMap> | |
905 | inline OutputIterator | |
906 | boruvka_mixed_merge(const Graph& g, WeightMap weight, OutputIterator out, | |
907 | GlobalIndexMap index) | |
908 | { | |
909 | typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor; | |
910 | typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type; | |
911 | std::vector<vertices_size_type> ranks(num_vertices(g)); | |
912 | std::vector<vertex_descriptor> parents(num_vertices(g)); | |
913 | std::vector<vertices_size_type> supervertex_indices(num_vertices(g)); | |
914 | ||
915 | return boruvka_mixed_merge | |
916 | (g, weight, out, index, | |
917 | make_iterator_property_map(ranks.begin(), index), | |
918 | make_iterator_property_map(parents.begin(), index), | |
919 | make_iterator_property_map(supervertex_indices.begin(), index)); | |
920 | } | |
921 | ||
922 | template<typename Graph, typename WeightMap, typename OutputIterator> | |
923 | inline OutputIterator | |
924 | boruvka_mixed_merge(const Graph& g, WeightMap weight, OutputIterator out) | |
925 | { return boruvka_mixed_merge(g, weight, out, get(vertex_index, g)); } | |
926 | ||
927 | } // end namespace distributed | |
928 | ||
929 | using distributed::dense_boruvka_minimum_spanning_tree; | |
930 | using distributed::merge_local_minimum_spanning_trees; | |
931 | using distributed::boruvka_then_merge; | |
932 | using distributed::boruvka_mixed_merge; | |
933 | ||
934 | } } // end namespace boost::graph | |
935 | ||
936 | ||
937 | #endif // BOOST_DEHNE_GOTZ_MIN_SPANNING_TREE_HPP |