diff --git a/include/boost/graph/graph_utility.hpp b/include/boost/graph/graph_utility.hpp index 4e5ef141a..66b3cce51 100644 --- a/include/boost/graph/graph_utility.hpp +++ b/include/boost/graph/graph_utility.hpp @@ -23,6 +23,8 @@ #include #include #include +// only for backward-compatibility error message +#include // iota moved to detail/algorithm.hpp #include @@ -339,26 +341,7 @@ inline bool is_reachable( return get(color, y) != color_traits< ColorValue >::white(); } -// Is the undirected graph connected? -// Is the directed graph strongly connected? -template < typename VertexListGraph, typename VertexColorMap > -inline bool is_connected(const VertexListGraph& g, VertexColorMap color) -{ - typedef typename property_traits< VertexColorMap >::value_type ColorValue; - typedef color_traits< ColorValue > Color; - typename graph_traits< VertexListGraph >::vertex_iterator ui, ui_end, vi, - vi_end, ci, ci_end; - for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) - for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) - if (*ui != *vi) - { - for (boost::tie(ci, ci_end) = vertices(g); ci != ci_end; ++ci) - put(color, *ci, Color::white()); - if (!is_reachable(*ui, *vi, g, color)) - return false; - } - return true; -} +// is_connected is moved to `is_connected.hpp` template < typename Graph > bool is_self_loop( diff --git a/include/boost/graph/is_connected.hpp b/include/boost/graph/is_connected.hpp new file mode 100644 index 000000000..8a34fe540 --- /dev/null +++ b/include/boost/graph/is_connected.hpp @@ -0,0 +1,241 @@ +#ifndef BOOST_GRAPH_IS_CONNECTED_HPP +#define BOOST_GRAPH_IS_CONNECTED_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost +{ + +struct connected_kind { + enum weak_t { weak }; + enum unilateral_t { unilateral }; + enum strong_t { strong }; + enum unspecified_t { unspecified }; +}; + +// used for static assert below +BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(has_value_type_ic, value_type, false) + +template < typename Graph, typename ConnectedKind = connected_kind::unspecified_t > +inline bool is_connected(const Graph& g, ConnectedKind kind = connected_kind::unspecified) +{ + // Issue error message if these functions are called with a ColorMap. + // This is to gracefully handle possible old usages of is_connected. + // todo: consider checking for ColorValueConcept as well + BOOST_STATIC_ASSERT_MSG( + (mpl::not_< has_value_type_ic< ConnectedKind > >::value), + "ColorMap argument to is_connected is deprecated. Omit the second " + "argument for undirected graphs or specify connected_kind::strong " + "to preserve the old behavior."); + + typedef typename graph_traits< Graph >::directed_category Cat; + BOOST_STATIC_ASSERT_MSG( + (mpl::not_< mpl::and_ < + is_same < ConnectedKind, connected_kind::unspecified_t >, + is_directed_graph< Graph > > >::value), + "connected_kind must be specified for directed graphs"); + + return is_connected_dispatch(g, Cat(), kind); +} + +// Undirected graph +template < typename IncidenceGraph, typename ConnectedKind > +inline bool is_connected_dispatch(const IncidenceGraph& g, undirected_tag, + ConnectedKind) +{ + // ignore the connection kind for undirected graph + return is_connected_undirected(g); +} + +template < typename IncidenceGraph > +inline bool is_connected_undirected(const IncidenceGraph& g) +{ + BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< IncidenceGraph >)); + + return is_connected_undirected(g, + make_two_bit_color_map(num_vertices(g), get(boost::vertex_index, g))); +} + +// color should start out white for every vertex +template < typename IncidenceGraph, typename VertexColorMap > +inline bool is_connected_undirected(const IncidenceGraph& g, + VertexColorMap colormap) +{ + typedef typename property_traits< VertexColorMap >::value_type ColorValue; + typedef color_traits< ColorValue > Color; + + default_dfs_visitor vis; + depth_first_visit(g, detail::get_default_starting_vertex(g), vis, colormap); + + // If an undirected graph is connected, then each vertex is reachable in a + // single DFS visit. If any vertex was unreachable, grpah is not connected. + typename graph_traits< IncidenceGraph >::vertex_iterator ui, ui_end; + for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) + if (get(colormap, *ui) == Color::white()) + return false; + return true; +} + +// Directed graph, strongly connected +template < typename IncidenceGraph > +inline bool is_connected_dispatch(const IncidenceGraph& g, directed_tag, + connected_kind::strong_t) +{ + return is_strongly_connected(g); +} + +template < typename Graph > +inline bool is_strongly_connected(const Graph& g) +{ + // A directed graph is stronly connected if and only if all its vertices + // are in a sinlge stronly connected component + + BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< Graph >)); + BOOST_STATIC_ASSERT((is_directed_graph< Graph >::value)); + + // Run the Tarjan's SCC algorithm + std::vector< size_t > comp_map(num_vertices(g)); + size_t num_scc = strong_components( + g, make_iterator_property_map(comp_map.begin(), get(vertex_index, g))); + + return num_scc == 1; +} + +// Directed graph, weakly connected +template < typename IncidenceGraph > +inline bool is_connected_dispatch(const IncidenceGraph& g, directed_tag, + connected_kind::weak_t) +{ + return is_weakly_connected(g); +} + +template < typename BidirectionalGraph > +inline bool is_weakly_connected(const BidirectionalGraph& g) +{ + BOOST_CONCEPT_ASSERT((BidirectionalGraphConcept< BidirectionalGraph >)); + + // For now do an undirected BFS walk + return is_weakly_connected(g, + make_two_bit_color_map(num_vertices(g), get(boost::vertex_index, g))); +} + +template < typename BidirectionalGraph , typename VertexColorMap > +inline bool is_weakly_connected(const BidirectionalGraph& g, VertexColorMap colormap) +{ + // A directed graph is weakly connected if and only if all its vertices + // can be reached in a single undirected BFS (or DFS) visit. + typedef typename property_traits< VertexColorMap >::value_type ColorValue; + typedef color_traits< ColorValue > Color; + + // todo: consider reimplementing this as DFS (is_connected above) over an + // as_undirected adaptor. + + neighbor_breadth_first_visit( + g, + detail::get_default_starting_vertex(g), + color_map(colormap) + ); + + typename graph_traits< BidirectionalGraph >::vertex_iterator ui, ui_end; + for (boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) + if (get(colormap, *ui) == Color::white()) + return false; + return true; +} + +// Directed graph, unilaterally connected +template < typename IncidenceGraph > +inline bool is_connected_dispatch(const IncidenceGraph& g, directed_tag, + connected_kind::unilateral_t) +{ + return is_unilaterally_connected(g); +} + +namespace detail { + + template < typename Graph > + struct unique_topological_order_visitor : public default_dfs_visitor + { + typedef typename graph_traits< Graph >::vertex_descriptor vertex_t; + vertex_t last_vertex; + bool& result; + + unique_topological_order_visitor(bool& result) + : last_vertex(graph_traits< Graph >::null_vertex()), result(result) + { + result = true; + } + + // Check that each finished vertex has an arrow to the last finished one + template < typename Vertex, typename G > + void finish_vertex(const Vertex& u, const G& g) + { + // todo: consider using TerminatorFunc or an exception to exit + // the DFS early (performance optimization) + if (result == false) + return; + + if (last_vertex != graph_traits< Graph >::null_vertex()) + { + if (!edge(u, last_vertex, g).second) + { + result = false; + } + } + last_vertex = u; + } + + }; + +} + +// Checks if the graph is in unique topological order, i.e. linear +template < typename Graph > +bool has_unique_topological_order(const Graph& g) +{ + bool result; + detail::unique_topological_order_visitor vis(result); + depth_first_search(g, visitor(vis)); + return result; +} + + +template < typename Graph > +inline bool is_unilaterally_connected(const Graph& g) +{ + // A directed graph is unilaterally connected if and only if its + // condensation graph is in unique topological order. + // Warning: condensation might be slow and consume 2x memory for the graph. + + BOOST_CONCEPT_ASSERT((IncidenceGraphConcept< Graph >)); + // See comment about renumber_vertex_indices above + // BOOST_CONCEPT_ASSERT((VertexIndexGraphConcept< Graph >)); + BOOST_STATIC_ASSERT((is_directed_graph< Graph >::value)); + + // Run the Tarjan's SCC algorithm + std::vector< size_t > comp_number(num_vertices(g)); + size_t num_scc = strong_components( + g, make_iterator_property_map(comp_number.begin(), get(vertex_index, g))); + if (num_scc == 1) // strongly connected case + return true; + + // Build the condensation graph + adjacency_list<> c; + std::vector< std::vector< typename graph_traits< Graph >::vertices_size_type > > components; + build_component_lists(g, num_scc, comp_number, components); + create_condensation_graph(g, components, comp_number, c); + // Check if the condensation is linear + return has_unique_topological_order(c); +} + + +} // namespace boost + +#endif /* BOOST_GRAPH_IS_CONNECTED_HPP */ + + diff --git a/include/boost/pending/detail/disjoint_sets.hpp b/include/boost/pending/detail/disjoint_sets.hpp index 9a91b294b..dc60baa8d 100644 --- a/include/boost/pending/detail/disjoint_sets.hpp +++ b/include/boost/pending/detail/disjoint_sets.hpp @@ -7,12 +7,14 @@ #define BOOST_DETAIL_DISJOINT_SETS_HPP #include +#include namespace boost { namespace detail { + using boost::get; template < class ParentPA, class Vertex > Vertex find_representative_with_path_halving(ParentPA p, Vertex v) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index b2656eb07..3736e7c53 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -159,6 +159,7 @@ alias graph_test_regular : [ run delete_edge.cpp ] [ run johnson-test.cpp ] [ run lvalue_pmap.cpp ] + [ run is_connected.cpp ] ; alias graph_test_with_filesystem : : diff --git a/test/is_connected.cpp b/test/is_connected.cpp new file mode 100644 index 000000000..d99da9efb --- /dev/null +++ b/test/is_connected.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +/* +#include +*/ +#include + +#include + +using namespace std; +using namespace boost; + +// todo: consider mdspan when it's widely implemented +inline size_t coord_to_idx(size_t height, size_t x, size_t y) +{ + return y * height + x; +} + +template < class Graph > +void fill_square_graph(Graph& graph, size_t size) +{ + const size_t W = size, H = size; + + for (size_t i = 0; i < W; ++i) + { + for (size_t j = 0; j < H; ++j) + { + size_t idx = coord_to_idx(H, i, j); + if (i > 0) + add_edge(coord_to_idx(H, i - 1, j), idx, graph); + if (j > 0) + add_edge(coord_to_idx(H, i, j - 1), idx, graph); + } + } +} + +int main(int argc, char* argv[]) +{ + // the side length of the square graph + size_t size = 20; + if (argc > 1) + { + size = lexical_cast< int >(argv[1]); + } + + { + // test is_strongly_connected + typedef adjacency_list<> dir_graph_t; + dir_graph_t g(size * size); + // the directed graph is not strongly connected + fill_square_graph(g, size); + + BOOST_TEST(is_connected(g, connected_kind::strong) == false); + BOOST_TEST(is_strongly_connected(g) == false); + BOOST_TEST(is_connected(g, connected_kind::unilateral) == false); + + // now make it strongly connected with one more edge from the last vertex + // to the first + add_edge(coord_to_idx(size, size - 1, size - 1), 0, g); + + BOOST_TEST(is_connected(g, connected_kind::strong) == true); + BOOST_TEST(is_strongly_connected(g) == true); + BOOST_TEST(is_connected(g, connected_kind::unilateral) == true); + } + + { + // test is_weakly_connected -- it requires bidirectional graph + typedef adjacency_list< vecS, vecS, bidirectionalS > bidir_graph_t; + bidir_graph_t g(size * size); + fill_square_graph(g, size); + // the directed graph is not strongly connected + BOOST_TEST(is_connected(g, connected_kind::strong) == false); + BOOST_TEST(is_strongly_connected(g) == false); + // but it is weakly connected + BOOST_TEST(is_connected(g, connected_kind::weak) == true); + BOOST_TEST(is_weakly_connected(g) == true); + + // another graph, 2 disconnected vertices + bidir_graph_t g2(2); + BOOST_TEST(is_connected(g2, connected_kind::weak) == false); + + // make it weakly connected + add_edge(0, 1, g2); + BOOST_TEST(is_connected(g2, connected_kind::weak) == true); + BOOST_TEST(is_connected(g2, connected_kind::strong) == false); + } + + { + // test undirected is_connected, it is the same for all connection kinds + typedef adjacency_list< vecS, vecS, undirectedS > undir_graph_t; + undir_graph_t g(size * size); + // the undirected graph is already strongly connected + fill_square_graph(g, size); + + BOOST_TEST(is_connected(g) == true); + BOOST_TEST(is_connected_undirected(g) == true); + BOOST_TEST(is_connected(g, connected_kind::strong) == true); + BOOST_TEST(is_connected(g, connected_kind::unilateral) == true); + BOOST_TEST(is_connected(g, connected_kind::weak) == true); + + // now fill fewer edges, so one row is disconnected + undir_graph_t g2(size * (size + 1)); + fill_square_graph(g2, size); + + BOOST_TEST(is_connected(g2) == false); + BOOST_TEST(is_connected_undirected(g2) == false); + BOOST_TEST(is_connected(g2, connected_kind::strong) == false); + BOOST_TEST(is_connected(g2, connected_kind::weak) == false); + + } + + { + // test all is_connected kinds + typedef adjacency_list< vecS, vecS, bidirectionalS > bidir_graph_t; + bidir_graph_t g(size * size); + fill_square_graph(g, size); + + BOOST_TEST(is_connected(g, connected_kind::weak) == true); + BOOST_TEST(is_weakly_connected(g) == true); + + BOOST_TEST(is_connected(g, connected_kind::strong) == false); + BOOST_TEST(is_strongly_connected(g) == false); + + BOOST_TEST(is_connected(g, connected_kind::unilateral) == false); + BOOST_TEST(is_unilaterally_connected(g) == false); + + bidir_graph_t g2(2); + BOOST_TEST(is_connected(g2, connected_kind::weak) == false); + BOOST_TEST(is_connected(g2, connected_kind::strong) == false); + BOOST_TEST(is_connected(g2, connected_kind::unilateral) == false); + + add_edge(0, 1, g2); + BOOST_TEST(is_connected(g2, connected_kind::weak) == true); + BOOST_TEST(is_connected(g2, connected_kind::strong) == false); + BOOST_TEST(is_connected(g2, connected_kind::unilateral) == true); + + + // test making a graph weakly, then unilaterally, then strongly connected + bidir_graph_t g3(3); + BOOST_TEST(is_connected(g3, connected_kind::weak) == false); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == false); + BOOST_TEST(is_connected(g3, connected_kind::strong) == false); + add_edge(0, 1, g3); + add_edge(2, 1, g3); + BOOST_TEST(is_connected(g3, connected_kind::weak) == true); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == false); + BOOST_TEST(is_connected(g3, connected_kind::strong) == false); + add_edge(0, 2, g3); + BOOST_TEST(is_connected(g3, connected_kind::weak) == true); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == true); + BOOST_TEST(is_connected(g3, connected_kind::strong) == false); + add_edge(1, 0, g3); + BOOST_TEST(is_connected(g3, connected_kind::weak) == true); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == true); + BOOST_TEST(is_connected(g3, connected_kind::strong) == true); + + // Test the usage of the old interface with ColorMap as the second arg. + // This causes static assertion. I don't see a way to test for it, so + // it's commented out. + // BOOST_TEST(is_connected(g3, two_bit_color_map<>(0)) == false); + + // Test that connected_kind is required for directed graph. + // This causes static assertion to fail as well. + // BOOST_TEST(is_connected(g3) == false); + } + + { + // check that we don't need the vertex_index + typedef adjacency_list< listS, listS, bidirectionalS > bidir_graph_t; + + } + + /* + { + // test that adjacency matrix implements bidirectional graph + // (see another PR [todo: link] + typedef boost::adjacency_matrix<> bidir_graph_t; + bidir_graph_t g(size * size); + fill_square_graph(g, size); + + BOOST_TEST(is_connected(g, connected_kind::weak) == true); + BOOST_TEST(is_weakly_connected(g) == true); + + BOOST_TEST(is_connected(g, connected_kind::strong) == false); + BOOST_TEST(is_strongly_connected(g) == false); + + BOOST_TEST(is_connected(g, connected_kind::unilateral) == false); + BOOST_TEST(is_unilaterally_connected(g) == false); + + bidir_graph_t g2(2); + BOOST_TEST(is_connected(g2, connected_kind::weak) == false); + BOOST_TEST(is_weakly_connected(g2) == false); + BOOST_TEST(is_connected(g2, connected_kind::strong) == false); + BOOST_TEST(is_strongly_connected(g2) == false); + BOOST_TEST(is_connected(g2, connected_kind::unilateral) == false); + BOOST_TEST(is_unilaterally_connected(g2) == false); + + add_edge(0, 1, g2); + BOOST_TEST(is_connected(g2, connected_kind::weak) == true); + BOOST_TEST(is_weakly_connected(g2) == true); + BOOST_TEST(is_connected(g2, connected_kind::strong) == false); + BOOST_TEST(is_strongly_connected(g2) == false); + BOOST_TEST(is_connected(g2, connected_kind::unilateral) == true); + BOOST_TEST(is_unilaterally_connected(g2) == true); + + bidir_graph_t g3(3); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == false); + add_edge(0, 1, g3); + add_edge(2, 1, g3); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == false); + add_edge(0, 2, g3); + BOOST_TEST(is_connected(g3, connected_kind::unilateral) == true); + } + */ + + // consider testing it if we decide to preserve the old interface + // test_colormap_reuse(size); + + return boost::report_errors(); +}