Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions include/boost/graph/vf2_sub_graph_iso.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,21 @@ template < typename Graph1, typename Graph2 > struct vf2_print_callback

namespace detail
{
template <typename Graph>
typename graph_traits<Graph>::vertices_size_type get_num_vertices(const Graph& g)
{
typedef typename graph_traits<Graph>::vertex_iterator iter;
std::pair<iter, iter> 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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
{
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Expand Down
86 changes: 86 additions & 0 deletions test/vf2_filtered_graph_test.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/graph/vf2_sub_graph_iso.hpp>
#include <boost/core/lightweight_test.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <cstddef>

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();
}
Loading