diff --git a/include/boost/graph/vf2_sub_graph_iso.hpp b/include/boost/graph/vf2_sub_graph_iso.hpp index b509cb499..5dbff3ee6 100644 --- a/include/boost/graph/vf2_sub_graph_iso.hpp +++ b/include/boost/graph/vf2_sub_graph_iso.hpp @@ -74,6 +74,21 @@ template < typename Graph1, typename Graph2 > struct vf2_print_callback namespace detail { + template + typename graph_traits::vertices_size_type get_num_vertices(const Graph& g) + { + typedef typename graph_traits::vertex_iterator iter; + std::pair vs = vertices(g); + return std::distance(vs.first, vs.second); + } + + // num_edges also counts the edges hidden by a filtered_graph + template < typename Graph > + typename graph_traits< Graph >::edges_size_type get_num_edges(const Graph& g) + { + const auto edge_range = edges(g); + return static_cast< typename graph_traits< Graph >::edges_size_type >(std::distance(edge_range.first, edge_range.second)); + } // State associated with a single graph (graph_this) template < typename GraphThis, typename GraphOther, typename IndexMapThis, @@ -445,6 +460,8 @@ namespace detail base_state< Graph1, Graph2, IndexMap1, IndexMap2 > state1_; base_state< Graph2, Graph1, IndexMap2, IndexMap1 > state2_; + graph1_size_type num_vertices1_; + // Three helper functions used in Feasibility and Valid functions to // test terminal set counts when testing for: // - graph sub-graph monomorphism, or @@ -483,6 +500,7 @@ namespace detail , vertex_comp_(vertex_comp) , state1_(graph1, graph2, index_map1, index_map2) , state2_(graph2, graph1, index_map2, index_map1) + , num_vertices1_(get_num_vertices(graph1)) { } @@ -701,7 +719,7 @@ namespace detail // Returns true if a mapping was found bool success() const { - return state1_.count() == num_vertices(graph1_); + return state1_.count() == num_vertices1_; } // Returns true if a state is valid @@ -753,7 +771,7 @@ namespace detail typename IndexMap2, typename VertexOrder1, typename EdgeEquivalencePredicate, typename VertexEquivalencePredicate, typename SubGraphIsoMapCallback, problem_selector problem_selection > - bool match(const Graph1& graph1, const Graph2& graph2, + bool match(const Graph1&, const Graph2& graph2, SubGraphIsoMapCallback user_callback, const VertexOrder1& vertex_order1, state< Graph1, Graph2, IndexMap1, IndexMap2, EdgeEquivalencePredicate, VertexEquivalencePredicate, SubGraphIsoMapCallback, @@ -975,20 +993,25 @@ namespace detail (BinaryPredicateConcept< VertexEquivalencePredicate, vertex_small_type, vertex_large_type >)); + typename graph_traits< GraphSmall >::vertices_size_type num_vertices_small + = get_num_vertices(graph_small); + typename graph_traits< GraphLarge >::vertices_size_type num_vertices_large + = get_num_vertices(graph_large); + // Vertex order requirements BOOST_CONCEPT_ASSERT((ContainerConcept< VertexOrderSmall >)); typedef typename VertexOrderSmall::value_type order_value_type; static_assert(std::is_same< vertex_small_type, order_value_type >::value, "The vertex type of the small graph must be the same as the order value type."); - BOOST_ASSERT(num_vertices(graph_small) == vertex_order_small.size()); + BOOST_ASSERT(num_vertices_small == vertex_order_small.size()); - if (num_vertices(graph_small) > num_vertices(graph_large)) + if (num_vertices_small > num_vertices_large) return false; typename graph_traits< GraphSmall >::edges_size_type num_edges_small - = num_edges(graph_small); + = get_num_edges(graph_small); typename graph_traits< GraphLarge >::edges_size_type num_edges_large - = num_edges(graph_large); + = get_num_edges(graph_large); // Double the number of edges for undirected graphs: each edge counts as // in-edge and out-edge @@ -1182,20 +1205,25 @@ bool vf2_graph_iso(const Graph1& graph1, const Graph2& graph2, BOOST_CONCEPT_ASSERT((BinaryPredicateConcept< VertexEquivalencePredicate, vertex1_type, vertex2_type >)); + typename graph_traits< Graph1 >::vertices_size_type num_vertices1 + = detail::get_num_vertices(graph1); + typename graph_traits< Graph2 >::vertices_size_type num_vertices2 + = detail::get_num_vertices(graph2); + // Vertex order requirements BOOST_CONCEPT_ASSERT((ContainerConcept< VertexOrder1 >)); typedef typename VertexOrder1::value_type order_value_type; static_assert(std::is_same< vertex1_type, order_value_type >::value, "The value type of the vertex order and the vertex type of the first graph must be the same type."); - BOOST_ASSERT(num_vertices(graph1) == vertex_order1.size()); - - if (num_vertices(graph1) != num_vertices(graph2)) + BOOST_ASSERT(num_vertices1 == vertex_order1.size()); + + if (num_vertices1 != num_vertices2) return false; typename graph_traits< Graph1 >::edges_size_type num_edges1 - = num_edges(graph1); + = detail::get_num_edges(graph1); typename graph_traits< Graph2 >::edges_size_type num_edges2 - = num_edges(graph2); + = detail::get_num_edges(graph2); // Double the number of edges for undirected graphs: each edge counts as // in-edge and out-edge diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index de369e014..ed78814ae 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -189,6 +189,7 @@ alias graph_test_regular : [ compile filtered_graph_properties_dijkstra.cpp ] [ run vf2_sub_graph_iso_test.cpp ] [ run vf2_sub_graph_iso_test_2.cpp ] + [ run vf2_filtered_graph_test.cpp ] [ run hawick_circuits.cpp ] [ run successive_shortest_path_nonnegative_weights_test.cpp ] [ run cycle_canceling_test.cpp ] diff --git a/test/vf2_filtered_graph_test.cpp b/test/vf2_filtered_graph_test.cpp new file mode 100644 index 000000000..bfe339898 --- /dev/null +++ b/test/vf2_filtered_graph_test.cpp @@ -0,0 +1,86 @@ +//======================================================================= +// Copyright (c) 2026 Arnaud Becheler +// +// Distributed under 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) +//======================================================================= + +#include +#include +#include +#include +#include + +using Graph = ::boost::adjacency_list< ::boost::vecS, ::boost::vecS, ::boost::undirectedS >; +using Vertex = ::boost::graph_traits< Graph >::vertex_descriptor; + +// Hides vertex 0 and therefore its edges. +struct hide_vertex_zero +{ + bool operator()(Vertex v) const { return v != 0; } +}; + +using FilteredGraph = ::boost::filtered_graph< Graph, ::boost::keep_all, hide_vertex_zero >; + +// Stops the search at the first mapping. +struct stop_at_first_mapping +{ + template < typename Map1To2, typename Map2To1 > + bool operator()(Map1To2, Map2To1) const + { + return false; + } +}; + +// Builds a triangle on vertices 1 2 3 with vertex 0 attached to vertex 1. +Graph make_base_graph() +{ + Graph g(4); + ::boost::add_edge(1, 2, g); + ::boost::add_edge(2, 3, g); + ::boost::add_edge(3, 1, g); + ::boost::add_edge(0, 1, g); + return g; +} + +// Builds the complete graph on n vertices. +Graph make_complete_graph(std::size_t n) +{ + Graph g(n); + for (std::size_t u = 0; u < n; ++u) + { + for (std::size_t v = u + 1; v < n; ++v) + { + ::boost::add_edge(u, v, g); + } + } + return g; +} + +// Checks that the filtered triangle is found inside the complete graph on 4 vertices. +void test_filtered_subgraph_iso() +{ + const Graph base = make_base_graph(); + const FilteredGraph g(base, ::boost::keep_all(), hide_vertex_zero()); + const Graph large = make_complete_graph(4); + + BOOST_TEST(::boost::vf2_subgraph_iso(g, large, stop_at_first_mapping())); +} + +// Checks that the filtered triangle is isomorphic to a triangle. +void test_filtered_graph_iso() +{ + const Graph base = make_base_graph(); + const FilteredGraph g(base, ::boost::keep_all(), hide_vertex_zero()); + const Graph triangle = make_complete_graph(3); + + BOOST_TEST(::boost::vf2_graph_iso(g, triangle, stop_at_first_mapping())); +} + +int main() +{ + test_filtered_subgraph_iso(); + test_filtered_graph_iso(); + return ::boost::report_errors(); +}