// Copyright (C) 2004-2006 The Trustees of Indiana University. // Use, modification and distribution is subject to the Boost Software // License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // Authors: Douglas Gregor // Andrew Lumsdaine /** * This header implements four distributed algorithms to compute * the minimum spanning tree (actually, minimum spanning forest) of a * graph. All of the algorithms were implemented as specified in the * paper by Dehne and Gotz: * * Frank Dehne and Silvia Gotz. Practical Parallel Algorithms for Minimum * Spanning Trees. In Symposium on Reliable Distributed Systems, * pages 366--371, 1998. * * There are four algorithm variants implemented. */ #ifndef BOOST_DEHNE_GOTZ_MIN_SPANNING_TREE_HPP #define BOOST_DEHNE_GOTZ_MIN_SPANNING_TREE_HPP #ifndef BOOST_GRAPH_USE_MPI #error "Parallel BGL files should not be included unless has been included" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace graph { namespace distributed { namespace detail { /** * Binary function object type that selects the (edge, weight) pair * with the minimum weight. Used within a Boruvka merge step to select * the candidate edges incident to each supervertex. */ struct smaller_weighted_edge { template std::pair operator()(const std::pair& x, const std::pair& y) const { return x.second < y.second? x : y; } }; /** * Unary predicate that determines if the source and target vertices * of the given edge have the same representative within a disjoint * sets data structure. Used to indicate when an edge is now a * self-loop because of supervertex merging in Boruvka's algorithm. */ template class do_has_same_supervertex { public: typedef typename graph_traits::edge_descriptor edge_descriptor; do_has_same_supervertex(DisjointSets& dset, const Graph& g) : dset(dset), g(g) { } bool operator()(edge_descriptor e) { return dset.find_set(source(e, g)) == dset.find_set(target(e, g)); } private: DisjointSets& dset; const Graph& g; }; /** * Build a @ref do_has_same_supervertex object. */ template inline do_has_same_supervertex has_same_supervertex(DisjointSets& dset, const Graph& g) { return do_has_same_supervertex(dset, g); } /** \brief A single distributed Boruvka merge step. * * A distributed Boruvka merge step involves computing (globally) * the minimum weight edges incident on each supervertex and then * merging supervertices along these edges. Once supervertices are * merged, self-loops are eliminated. * * The set of parameters passed to this algorithm is large, and * considering this algorithm in isolation there are several * redundancies. However, the more asymptotically efficient * distributed MSF algorithms require mixing Boruvka steps with the * merging of local MSFs (implemented in * merge_local_minimum_spanning_trees_step): the interaction of the * two algorithms mandates the addition of these parameters. * * \param pg The process group over which communication should be * performed. Within the distributed Boruvka algorithm, this will be * equivalent to \code process_group(g); however, in the context of * the mixed MSF algorithms, the process group @p pg will be a * (non-strict) process subgroup of \code process_group(g). * * \param g The underlying graph on which the MSF is being * computed. The type of @p g must model DistributedGraph, but there * are no other requirements because the edge and (super)vertex * lists are passed separately. * * \param weight_map Property map containing the weights of each * edge. The type of this property map must model * ReadablePropertyMap and must support caching. * * \param out An output iterator that will be written with the set * of edges selected to build the MSF. Every process within the * process group @p pg will receive all edges in the MSF. * * \param dset Disjoint sets data structure mapping from vertices in * the graph @p g to their representative supervertex. * * \param supervertex_map Mapping from supervertex descriptors to * indices. * * \param supervertices A vector containing all of the * supervertices. Will be modified to include only the remaining * supervertices after merging occurs. * * \param edge_list The list of edges that remain in the graph. This * list will be pruned to remove self-loops once MSF edges have been * found. */ template OutputIterator boruvka_merge_step(ProcessGroup pg, const Graph& g, WeightMap weight_map, OutputIterator out, disjoint_sets& dset, SupervertexMap supervertex_map, std::vector& supervertices, EdgeList& edge_list) { typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::vertices_size_type vertices_size_type; typedef typename graph_traits::edge_descriptor edge_descriptor; typedef typename EdgeList::iterator edge_iterator; typedef typename property_traits::value_type weight_type; typedef boost::parallel::detail::untracked_pair w_edge; typedef typename property_traits::value_type supervertex_index; smaller_weighted_edge min_edge; weight_type inf = (std::numeric_limits::max)(); // Renumber the supervertices for (std::size_t i = 0; i < supervertices.size(); ++i) put(supervertex_map, supervertices[i], i); // BSP-B1: Find local minimum-weight edges for each supervertex std::vector candidate_edges(supervertices.size(), w_edge(edge_descriptor(), inf)); for (edge_iterator ei = edge_list.begin(); ei != edge_list.end(); ++ei) { weight_type w = get(weight_map, *ei); supervertex_index u = get(supervertex_map, dset.find_set(source(*ei, g))); supervertex_index v = get(supervertex_map, dset.find_set(target(*ei, g))); if (u != v) { candidate_edges[u] = min_edge(candidate_edges[u], w_edge(*ei, w)); candidate_edges[v] = min_edge(candidate_edges[v], w_edge(*ei, w)); } } // BSP-B2 (a): Compute global minimum edges for each supervertex all_reduce(pg, &candidate_edges[0], &candidate_edges[0] + candidate_edges.size(), &candidate_edges[0], min_edge); // BSP-B2 (b): Use the edges to compute sequentially the new // connected components and emit the edges. for (vertices_size_type i = 0; i < candidate_edges.size(); ++i) { if (candidate_edges[i].second != inf) { edge_descriptor e = candidate_edges[i].first; vertex_descriptor u = dset.find_set(source(e, g)); vertex_descriptor v = dset.find_set(target(e, g)); if (u != v) { // Emit the edge, but cache the weight so everyone knows it cache(weight_map, e, candidate_edges[i].second); *out++ = e; // Link the two supervertices dset.link(u, v); // Whichever vertex was reparented will be removed from the // list of supervertices. vertex_descriptor victim = u; if (dset.find_set(u) == u) victim = v; supervertices[get(supervertex_map, victim)] = graph_traits::null_vertex(); } } } // BSP-B3: Eliminate self-loops edge_list.erase(std::remove_if(edge_list.begin(), edge_list.end(), has_same_supervertex(dset, g)), edge_list.end()); // TBD: might also eliminate multiple edges between supervertices // when the edges do not have the best weight, but this is not // strictly necessary. // Eliminate supervertices that have been absorbed supervertices.erase(std::remove(supervertices.begin(), supervertices.end(), graph_traits::null_vertex()), supervertices.end()); return out; } /** * An edge descriptor adaptor that reroutes the source and target * edges to different vertices, but retains the original edge * descriptor for, e.g., property maps. This is used when we want to * turn a set of edges in the overall graph into a set of edges * between supervertices. */ template struct supervertex_edge_descriptor { typedef supervertex_edge_descriptor self_type; typedef typename graph_traits::vertex_descriptor Vertex; typedef typename graph_traits::edge_descriptor Edge; Vertex source; Vertex target; Edge e; operator Edge() const { return e; } friend inline bool operator==(const self_type& x, const self_type& y) { return x.e == y.e; } friend inline bool operator!=(const self_type& x, const self_type& y) { return x.e != y.e; } }; template inline typename supervertex_edge_descriptor::Vertex source(supervertex_edge_descriptor se, const Graph&) { return se.source; } template inline typename supervertex_edge_descriptor::Vertex target(supervertex_edge_descriptor se, const Graph&) { return se.target; } /** * Build a supervertex edge descriptor from a normal edge descriptor * using the given disjoint sets data structure to identify * supervertex representatives. */ template struct build_supervertex_edge_descriptor { typedef typename graph_traits::vertex_descriptor Vertex; typedef typename graph_traits::edge_descriptor Edge; typedef Edge argument_type; typedef supervertex_edge_descriptor result_type; build_supervertex_edge_descriptor() : g(0), dsets(0) { } build_supervertex_edge_descriptor(const Graph& g, DisjointSets& dsets) : g(&g), dsets(&dsets) { } result_type operator()(argument_type e) const { result_type result; result.source = dsets->find_set(source(e, *g)); result.target = dsets->find_set(target(e, *g)); result.e = e; return result; } private: const Graph* g; DisjointSets* dsets; }; template inline build_supervertex_edge_descriptor make_supervertex_edge_descriptor(const Graph& g, DisjointSets& dsets) { return build_supervertex_edge_descriptor(g, dsets); } template struct identity_function { typedef T argument_type; typedef T result_type; result_type operator()(argument_type x) const { return x; } }; template class is_not_msf_edge { typedef typename graph_traits::vertex_descriptor Vertex; typedef typename graph_traits::edge_descriptor Edge; public: is_not_msf_edge(const Graph& g, DisjointSets dset, EdgeMapper edge_mapper) : g(g), dset(dset), edge_mapper(edge_mapper) { } bool operator()(Edge e) { Vertex u = dset.find_set(source(edge_mapper(e), g)); Vertex v = dset.find_set(target(edge_mapper(e), g)); if (u == v) return true; else { dset.link(u, v); return false; } } private: const Graph& g; DisjointSets dset; EdgeMapper edge_mapper; }; template void sorted_mutating_kruskal(const Graph& g, ForwardIterator first_vertex, ForwardIterator last_vertex, EdgeList& edge_list, EdgeMapper edge_mapper, RankMap rank_map, ParentMap parent_map) { typedef disjoint_sets DisjointSets; // Build and initialize disjoint-sets data structure DisjointSets dset(rank_map, parent_map); for (ForwardIterator v = first_vertex; v != last_vertex; ++v) dset.make_set(*v); is_not_msf_edge remove_non_msf_edges(g, dset, edge_mapper); edge_list.erase(std::remove_if(edge_list.begin(), edge_list.end(), remove_non_msf_edges), edge_list.end()); } /** * Merge local minimum spanning forests from p processes into * minimum spanning forests on p/D processes (where D is the tree * factor, currently fixed at 3), eliminating unnecessary edges in * the process. * * As with @ref boruvka_merge_step, this routine has many * parameters, not all of which make sense within the limited * context of this routine. The parameters are required for the * Boruvka and local MSF merging steps to interoperate. * * \param pg The process group on which local minimum spanning * forests should be merged. The top (D-1)p/D processes will be * eliminated, and a new process subgroup containing p/D processors * will be returned. The value D is a constant factor that is * currently fixed to 3. * * \param g The underlying graph whose MSF is being computed. It must model * the DistributedGraph concept. * * \param first_vertex Iterator to the first vertex in the graph * that should be considered. While the local MSF merging algorithm * typically operates on the entire vertex set, within the hybrid * distributed MSF algorithms this will refer to the first * supervertex. * * \param last_vertex The past-the-end iterator for the vertex list. * * \param edge_list The list of local edges that will be * considered. For the p/D processes that remain, this list will * contain edges in the MSF known to the vertex after other * processes' edge lists have been merged. The edge list must be * sorted in order of increasing weight. * * \param weight Property map containing the weights of each * edge. The type of this property map must model * ReadablePropertyMap and must support caching. * * \param global_index Mapping from vertex descriptors to a global * index. The type must model ReadablePropertyMap. * * \param edge_mapper A function object that can remap edge descriptors * in the edge list to any alternative edge descriptor. This * function object will be the identity function when a pure merging * of local MSFs is required, but may be a mapping to a supervertex * edge when the local MSF merging occurs on a supervertex * graph. This function object saves us the trouble of having to * build a supervertex graph adaptor. * * \param already_local_msf True when the edge list already * constitutes a local MSF. If false, Kruskal's algorithm will first * be applied to the local edge list to select MSF edges. * * \returns The process subgroup containing the remaining p/D * processes. If the size of this process group is greater than one, * the MSF edges contained in the edge list do not constitute an MSF * for the entire graph. */ template ProcessGroup merge_local_minimum_spanning_trees_step(ProcessGroup pg, const Graph& g, ForwardIterator first_vertex, ForwardIterator last_vertex, EdgeList& edge_list, WeightMap weight, GlobalIndexMap global_index, EdgeMapper edge_mapper, bool already_local_msf) { typedef typename ProcessGroup::process_id_type process_id_type; typedef typename EdgeList::value_type edge_descriptor; typedef typename property_traits::value_type weight_type; typedef typename graph_traits::vertex_descriptor vertex_descriptor; // The tree factor, often called "D" process_id_type const tree_factor = 3; process_id_type num_procs = num_processes(pg); process_id_type id = process_id(pg); process_id_type procs_left = (num_procs + tree_factor - 1) / tree_factor; std::size_t n = std::size_t(last_vertex - first_vertex); if (!already_local_msf) { // Compute local minimum spanning forest. We only care about the // edges in the MSF, because only edges in the local MSF can be in // the global MSF. std::vector ranks(n); std::vector parents(n); detail::sorted_mutating_kruskal (g, first_vertex, last_vertex, edge_list, edge_mapper, make_iterator_property_map(ranks.begin(), global_index), make_iterator_property_map(parents.begin(), global_index)); } typedef std::pair w_edge; // Order edges based on their weights. indirect_cmp > cmp_edge_weight(weight); if (id < procs_left) { // The p/D processes that remain will receive local MSF edges from // D-1 other processes. synchronize(pg); for (process_id_type from_id = procs_left + id; from_id < num_procs; from_id += procs_left) { std::size_t num_incoming_edges; receive(pg, from_id, 0, num_incoming_edges); if (num_incoming_edges > 0) { std::vector incoming_edges(num_incoming_edges); receive(pg, from_id, 1, &incoming_edges[0], num_incoming_edges); edge_list.reserve(edge_list.size() + num_incoming_edges); for (std::size_t i = 0; i < num_incoming_edges; ++i) { cache(weight, incoming_edges[i].first, incoming_edges[i].second); edge_list.push_back(incoming_edges[i].first); } std::inplace_merge(edge_list.begin(), edge_list.end() - num_incoming_edges, edge_list.end(), cmp_edge_weight); } } // Compute the local MSF from union of the edges in the MSFs of // all children. std::vector ranks(n); std::vector parents(n); detail::sorted_mutating_kruskal (g, first_vertex, last_vertex, edge_list, edge_mapper, make_iterator_property_map(ranks.begin(), global_index), make_iterator_property_map(parents.begin(), global_index)); } else { // The (D-1)p/D processes that are dropping out of further // computations merely send their MSF edges to their parent // process in the process tree. send(pg, id % procs_left, 0, edge_list.size()); if (edge_list.size() > 0) { std::vector outgoing_edges; outgoing_edges.reserve(edge_list.size()); for (std::size_t i = 0; i < edge_list.size(); ++i) { outgoing_edges.push_back(std::make_pair(edge_list[i], get(weight, edge_list[i]))); } send(pg, id % procs_left, 1, &outgoing_edges[0], outgoing_edges.size()); } synchronize(pg); } // Return a process subgroup containing the p/D parent processes return process_subgroup(pg, make_counting_iterator(process_id_type(0)), make_counting_iterator(procs_left)); } } // end namespace detail // --------------------------------------------------------------------- // Dense Boruvka MSF algorithm // --------------------------------------------------------------------- template OutputIterator dense_boruvka_minimum_spanning_tree(const Graph& g, WeightMap weight_map, OutputIterator out, VertexIndexMap index_map, RankMap rank_map, ParentMap parent_map, SupervertexMap supervertex_map) { using boost::graph::parallel::process_group; typedef typename graph_traits::traversal_category traversal_category; BOOST_STATIC_ASSERT((is_convertible::value)); typedef typename graph_traits::vertices_size_type vertices_size_type; typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::vertex_iterator vertex_iterator; typedef typename graph_traits::edge_descriptor edge_descriptor; // Don't throw away cached edge weights weight_map.set_max_ghost_cells(0); // Initialize the disjoint sets structures disjoint_sets dset(rank_map, parent_map); vertex_iterator vi, vi_end; for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) dset.make_set(*vi); std::vector supervertices; supervertices.assign(vertices(g).first, vertices(g).second); // Use Kruskal's algorithm to find the minimum spanning forest // considering only the local edges. The resulting edges are not // necessarily going to be in the final minimum spanning // forest. However, any edge not part of the local MSF cannot be a // part of the global MSF, so we should have eliminated some edges // from consideration. std::vector edge_list; kruskal_minimum_spanning_tree (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, edges(g).first, edges(g).second), std::back_inserter(edge_list), boost::weight_map(weight_map). vertex_index_map(index_map)); // While the number of supervertices is decreasing, keep executing // Boruvka steps to identify additional MSF edges. This loop will // execute log |V| times. vertices_size_type old_num_supervertices; do { old_num_supervertices = supervertices.size(); out = detail::boruvka_merge_step(process_group(g), g, weight_map, out, dset, supervertex_map, supervertices, edge_list); } while (supervertices.size() < old_num_supervertices); return out; } template OutputIterator dense_boruvka_minimum_spanning_tree(const Graph& g, WeightMap weight_map, OutputIterator out, VertexIndex i_map) { typedef typename graph_traits::vertex_descriptor vertex_descriptor; std::vector ranks(num_vertices(g)); std::vector parents(num_vertices(g)); std::vector supervertices(num_vertices(g)); return dense_boruvka_minimum_spanning_tree (g, weight_map, out, i_map, make_iterator_property_map(ranks.begin(), i_map), make_iterator_property_map(parents.begin(), i_map), make_iterator_property_map(supervertices.begin(), i_map)); } template OutputIterator dense_boruvka_minimum_spanning_tree(const Graph& g, WeightMap weight_map, OutputIterator out) { return dense_boruvka_minimum_spanning_tree(g, weight_map, out, get(vertex_index, g)); } // --------------------------------------------------------------------- // Merge local MSFs MSF algorithm // --------------------------------------------------------------------- template OutputIterator merge_local_minimum_spanning_trees(const Graph& g, WeightMap weight, OutputIterator out, GlobalIndexMap global_index) { using boost::graph::parallel::process_group_type; using boost::graph::parallel::process_group; typedef typename graph_traits::traversal_category traversal_category; BOOST_STATIC_ASSERT((is_convertible::value)); typedef typename graph_traits::edge_descriptor edge_descriptor; // Don't throw away cached edge weights weight.set_max_ghost_cells(0); // Compute the initial local minimum spanning forests std::vector edge_list; kruskal_minimum_spanning_tree (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, edges(g).first, edges(g).second), std::back_inserter(edge_list), boost::weight_map(weight).vertex_index_map(global_index)); // Merge the local MSFs from p processes into p/D processes, // reducing the number of processes in each step. Continue looping // until either (a) the current process drops out or (b) only one // process remains in the group. This loop will execute log_D p // times. typename process_group_type::type pg = process_group(g); while (pg && num_processes(pg) > 1) { pg = detail::merge_local_minimum_spanning_trees_step (pg, g, vertices(g).first, vertices(g).second, edge_list, weight, global_index, detail::identity_function(), true); } // Only process 0 has the entire edge list, so emit it to the output // iterator. if (pg && process_id(pg) == 0) { out = std::copy(edge_list.begin(), edge_list.end(), out); } synchronize(process_group(g)); return out; } template inline OutputIterator merge_local_minimum_spanning_trees(const Graph& g, WeightMap weight, OutputIterator out) { return merge_local_minimum_spanning_trees(g, weight, out, get(vertex_index, g)); } // --------------------------------------------------------------------- // Boruvka-then-merge MSF algorithm // --------------------------------------------------------------------- template OutputIterator boruvka_then_merge(const Graph& g, WeightMap weight, OutputIterator out, GlobalIndexMap index, RankMap rank_map, ParentMap parent_map, SupervertexMap supervertex_map) { using std::log; using boost::graph::parallel::process_group_type; using boost::graph::parallel::process_group; typedef typename graph_traits::traversal_category traversal_category; BOOST_STATIC_ASSERT((is_convertible::value)); typedef typename graph_traits::vertices_size_type vertices_size_type; typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::vertex_iterator vertex_iterator; typedef typename graph_traits::edge_descriptor edge_descriptor; // Don't throw away cached edge weights weight.set_max_ghost_cells(0); // Compute the initial local minimum spanning forests std::vector edge_list; kruskal_minimum_spanning_tree (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, edges(g).first, edges(g).second), std::back_inserter(edge_list), boost::weight_map(weight). vertex_index_map(index)); // Initialize the disjoint sets structures for Boruvka steps disjoint_sets dset(rank_map, parent_map); vertex_iterator vi, vi_end; for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) dset.make_set(*vi); // Construct the initial set of supervertices (all vertices) std::vector supervertices; supervertices.assign(vertices(g).first, vertices(g).second); // Continue performing Boruvka merge steps until the number of // supervertices reaches |V| / (log_D p)^2. const std::size_t tree_factor = 3; // TBD: same as above! should be param double log_d_p = log((double)num_processes(process_group(g))) / log((double)tree_factor); vertices_size_type target_supervertices = vertices_size_type(num_vertices(g) / (log_d_p * log_d_p)); vertices_size_type old_num_supervertices; while (supervertices.size() > target_supervertices) { old_num_supervertices = supervertices.size(); out = detail::boruvka_merge_step(process_group(g), g, weight, out, dset, supervertex_map, supervertices, edge_list); if (supervertices.size() == old_num_supervertices) return out; } // Renumber the supervertices for (std::size_t i = 0; i < supervertices.size(); ++i) put(supervertex_map, supervertices[i], i); // Merge local MSFs on the supervertices. (D-1)p/D processors drop // out each iteration, so this loop executes log_D p times. typename process_group_type::type pg = process_group(g); bool have_msf = false; while (pg && num_processes(pg) > 1) { pg = detail::merge_local_minimum_spanning_trees_step (pg, g, supervertices.begin(), supervertices.end(), edge_list, weight, supervertex_map, detail::make_supervertex_edge_descriptor(g, dset), have_msf); have_msf = true; } // Only process 0 has the complete list of _supervertex_ MST edges, // so emit those to the output iterator. This is not the complete // list of edges in the MSF, however: the Boruvka steps in the // beginning of the algorithm emitted any edges used to merge // supervertices. if (pg && process_id(pg) == 0) out = std::copy(edge_list.begin(), edge_list.end(), out); synchronize(process_group(g)); return out; } template inline OutputIterator boruvka_then_merge(const Graph& g, WeightMap weight, OutputIterator out, GlobalIndexMap index) { typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::vertices_size_type vertices_size_type; std::vector ranks(num_vertices(g)); std::vector parents(num_vertices(g)); std::vector supervertex_indices(num_vertices(g)); return boruvka_then_merge (g, weight, out, index, make_iterator_property_map(ranks.begin(), index), make_iterator_property_map(parents.begin(), index), make_iterator_property_map(supervertex_indices.begin(), index)); } template inline OutputIterator boruvka_then_merge(const Graph& g, WeightMap weight, OutputIterator out) { return boruvka_then_merge(g, weight, out, get(vertex_index, g)); } // --------------------------------------------------------------------- // Boruvka-mixed-merge MSF algorithm // --------------------------------------------------------------------- template OutputIterator boruvka_mixed_merge(const Graph& g, WeightMap weight, OutputIterator out, GlobalIndexMap index, RankMap rank_map, ParentMap parent_map, SupervertexMap supervertex_map) { using boost::graph::parallel::process_group_type; using boost::graph::parallel::process_group; typedef typename graph_traits::traversal_category traversal_category; BOOST_STATIC_ASSERT((is_convertible::value)); typedef typename graph_traits::vertices_size_type vertices_size_type; typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::vertex_iterator vertex_iterator; typedef typename graph_traits::edge_descriptor edge_descriptor; // Don't throw away cached edge weights weight.set_max_ghost_cells(0); // Initialize the disjoint sets structures for Boruvka steps disjoint_sets dset(rank_map, parent_map); vertex_iterator vi, vi_end; for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) dset.make_set(*vi); // Construct the initial set of supervertices (all vertices) std::vector supervertices; supervertices.assign(vertices(g).first, vertices(g).second); // Compute the initial local minimum spanning forests std::vector edge_list; kruskal_minimum_spanning_tree (make_vertex_and_edge_range(g, vertices(g).first, vertices(g).second, edges(g).first, edges(g).second), std::back_inserter(edge_list), boost::weight_map(weight). vertex_index_map(index)); if (num_processes(process_group(g)) == 1) { return std::copy(edge_list.begin(), edge_list.end(), out); } // Like the merging local MSFs algorithm and the Boruvka-then-merge // algorithm, each iteration of this loop reduces the number of // processes by a constant factor D, and therefore we require log_D // p iterations. Note also that the number of edges in the edge list // decreases geometrically, giving us an efficient distributed MSF // algorithm. typename process_group_type::type pg = process_group(g); vertices_size_type old_num_supervertices; while (pg && num_processes(pg) > 1) { // A single Boruvka step. If this doesn't change anything, we're done old_num_supervertices = supervertices.size(); out = detail::boruvka_merge_step(pg, g, weight, out, dset, supervertex_map, supervertices, edge_list); if (old_num_supervertices == supervertices.size()) { edge_list.clear(); break; } // Renumber the supervertices for (std::size_t i = 0; i < supervertices.size(); ++i) put(supervertex_map, supervertices[i], i); // A single merging of local MSTs, which reduces the number of // processes we're using by a constant factor D. pg = detail::merge_local_minimum_spanning_trees_step (pg, g, supervertices.begin(), supervertices.end(), edge_list, weight, supervertex_map, detail::make_supervertex_edge_descriptor(g, dset), true); } // Only process 0 has the complete edge list, so emit it for the // user. Note that list edge list only contains the MSF edges in the // final supervertex graph: all of the other edges were used to // merge supervertices and have been emitted by the Boruvka steps, // although only process 0 has received the complete set. if (pg && process_id(pg) == 0) out = std::copy(edge_list.begin(), edge_list.end(), out); synchronize(process_group(g)); return out; } template inline OutputIterator boruvka_mixed_merge(const Graph& g, WeightMap weight, OutputIterator out, GlobalIndexMap index) { typedef typename graph_traits::vertex_descriptor vertex_descriptor; typedef typename graph_traits::vertices_size_type vertices_size_type; std::vector ranks(num_vertices(g)); std::vector parents(num_vertices(g)); std::vector supervertex_indices(num_vertices(g)); return boruvka_mixed_merge (g, weight, out, index, make_iterator_property_map(ranks.begin(), index), make_iterator_property_map(parents.begin(), index), make_iterator_property_map(supervertex_indices.begin(), index)); } template inline OutputIterator boruvka_mixed_merge(const Graph& g, WeightMap weight, OutputIterator out) { return boruvka_mixed_merge(g, weight, out, get(vertex_index, g)); } } // end namespace distributed using distributed::dense_boruvka_minimum_spanning_tree; using distributed::merge_local_minimum_spanning_trees; using distributed::boruvka_then_merge; using distributed::boruvka_mixed_merge; } } // end namespace boost::graph #endif // BOOST_DEHNE_GOTZ_MIN_SPANNING_TREE_HPP