From 5ec43278169708a77586d347d734d72c370b3448 Mon Sep 17 00:00:00 2001 From: Jan-Grimo Sobez Date: Thu, 7 Sep 2023 19:25:47 +0200 Subject: [PATCH] Invariant contiguous range requirement removal Invariant contiguous range requirement removal - Vertex invariants for use in isomorphism algorithm must no longer have low upper bounds due to a hidden allocation linear in the maximum encountered vertex invariant. - Vertex invariants must no longer be convertible to `size_t`, but can be any comparable and hashable types - Build `unordered_map`-backed invariant multiplicity map efficiently from sorted vertex invariants --- doc/isomorphism.html | 5 +- include/boost/graph/isomorphism.hpp | 119 +++++++++++++++++++--------- 2 files changed, 82 insertions(+), 42 deletions(-) diff --git a/doc/isomorphism.html b/doc/isomorphism.html index 40a9867b..524ce9c7 100644 --- a/doc/isomorphism.html +++ b/doc/isomorphism.html @@ -114,9 +114,8 @@ equal) or to impose extra conditions on the result. The href="http://www.boost.org/sgi/stl/AdaptableUnaryFunction.html">AdaptableUnaryFunction, with the argument type of vertex_invariant1 being Graph1's vertex descriptor type, the argument type of vertex_invariant2 being -Graph2's vertex descriptor type, and both functions having integral -result types. The values returned by these two functions must have a low upper -bound, as memory linear in the maximal invariant value is allocated. +Graph2's vertex descriptor type, and both functions sharing a +result type that is totally ordered and hashable, such as an integer.
Default: degree_vertex_invariant for both arguments
Python: Unsupported parameter. diff --git a/include/boost/graph/isomorphism.hpp b/include/boost/graph/isomorphism.hpp index 060410f2..c2d696c8 100644 --- a/include/boost/graph/isomorphism.hpp +++ b/include/boost/graph/isomorphism.hpp @@ -30,6 +30,22 @@ namespace boost namespace detail { + template < typename T > + struct HashableConcept { + BOOST_CONCEPT_USAGE(HashableConcept) + { + hash hasher; + typedef typename hash::result_type hash_result; + hash_result val = hasher(t); + boost::ignore_unused_variable_warning(val); + } + + T t; + }; + + template < typename Invariant > + struct InvariantConcept : LessThanComparable, EqualityComparable, HashableConcept {}; + template < typename Graph1, typename Graph2, typename IsoMapping, typename Invariant1, typename Invariant2, typename IndexMap1, typename IndexMap2 > @@ -39,8 +55,8 @@ namespace detail typedef typename graph_traits< Graph2 >::vertex_descriptor vertex2_t; typedef typename graph_traits< Graph1 >::edge_descriptor edge1_t; typedef typename graph_traits< Graph1 >::vertices_size_type size_type; - typedef typename Invariant1::result_type invar1_value; - typedef typename Invariant2::result_type invar2_value; + typedef typename Invariant1::result_type invariant_t; + typedef unordered_map< invariant_t, size_type > multiplicity_map; const Graph1& G1; const Graph2& G2; @@ -81,17 +97,17 @@ namespace detail friend struct compare_multiplicity; struct compare_multiplicity { - compare_multiplicity(Invariant1 invariant1, size_type* multiplicity) + compare_multiplicity(Invariant1 invariant1, const multiplicity_map& multiplicity) : invariant1(invariant1), multiplicity(multiplicity) { } bool operator()(const vertex1_t& x, const vertex1_t& y) const { - return multiplicity[invariant1(x)] - < multiplicity[invariant1(y)]; + return multiplicity.at(invariant1(x)) + < multiplicity.at(invariant1(y)); } Invariant1 invariant1; - size_type* multiplicity; + const multiplicity_map& multiplicity; }; struct record_dfs_order : default_dfs_visitor @@ -158,46 +174,65 @@ namespace detail ); } + // Generates map of invariant multiplicity from sorted invariants + multiplicity_map multiplicities(const std::vector< invariant_t >& invariants) { + // Assumes invariants are sorted + multiplicity_map invar_multiplicity; + + typedef typename std::vector< invariant_t >::const_iterator invar_iter; + typedef typename multiplicity_map::iterator invar_map_iter; + invar_iter it = invariants.begin(); + const invar_iter end = invariants.end(); + + if(it == end) { + return invar_multiplicity; + } + + invariant_t invar = *it; + invar_map_iter inserted = invar_multiplicity.emplace(invar, 1).first; + ++it; + for(; it != end; ++it) + { + if(*it == invar) + { + inserted->second += 1; + } + else + { + invar = *it; + inserted = invar_multiplicity.emplace(invar, 1).first; + } + } + + return invar_multiplicity; + } + bool test_isomorphism() { // reset isomapping BGL_FORALL_VERTICES_T(v, G1, Graph1) f[v] = graph_traits< Graph2 >::null_vertex(); - std::size_t max_invariant = 0; - { - std::vector< invar1_value > invar1_array; - invar1_array.reserve(num_vertices(G1)); - BGL_FORALL_VERTICES_T(v, G1, Graph1) - invar1_array.push_back(invariant1(v)); - sort(invar1_array); + // Calculate all invariants of G1 and G2, sort and compare + std::vector< invariant_t > invar1_array; + invar1_array.reserve(num_vertices(G1)); + BGL_FORALL_VERTICES_T(v, G1, Graph1) + invar1_array.push_back(invariant1(v)); + sort(invar1_array); - std::vector< invar2_value > invar2_array; - invar2_array.reserve(num_vertices(G2)); - BGL_FORALL_VERTICES_T(v, G2, Graph2) - invar2_array.push_back(invariant2(v)); - sort(invar2_array); - if (!equal(invar1_array, invar2_array)) - return false; - - // Empty graphs case is covered before test_isomorphism is - // called, so the invar?_arrays cannot be empty, so back() is - // safe. Also the two invariant arrays are equal: - max_invariant = invar1_array.back(); - assert(max_invariant != std::numeric_limits::max()); - max_invariant += 1; - } + std::vector< invariant_t > invar2_array; + invar2_array.reserve(num_vertices(G2)); + BGL_FORALL_VERTICES_T(v, G2, Graph2) + invar2_array.push_back(invariant2(v)); + sort(invar2_array); + if (!equal(invar1_array, invar2_array)) + return false; + // Sort vertices by the multiplicity of their invariants std::vector< vertex1_t > V_mult; BGL_FORALL_VERTICES_T(v, G1, Graph1) V_mult.push_back(v); - { - std::vector< size_type > multiplicity(max_invariant, 0); - BGL_FORALL_VERTICES_T(v, G1, Graph1) - ++multiplicity.at(invariant1(v)); - sort( - V_mult, compare_multiplicity(invariant1, &multiplicity[0])); - } + sort(V_mult, compare_multiplicity(invariant1, multiplicities(invar1_array))); std::vector< default_color_type > color_vec(num_vertices(G1)); safe_iterator_property_map< @@ -437,7 +472,7 @@ namespace detail } if(!unmatched_g1_vertices.empty()) { - typedef unordered_multimap< invar2_value, vertex2_t > g2_invariant_vertex_multimap; + typedef unordered_multimap< invariant_t, vertex2_t > g2_invariant_vertex_multimap; typedef typename g2_invariant_vertex_multimap::iterator multimap_iter; g2_invariant_vertex_multimap unmatched_invariants; BGL_FORALL_VERTICES_T(v, G2, Graph2) @@ -451,7 +486,7 @@ namespace detail const v1_iter end = unmatched_g1_vertices.end(); for(v1_iter iter = unmatched_g1_vertices.begin(); iter != end; ++iter) { - invar1_value unmatched_g1_vertex_invariant = invariant1(*iter); + invariant_t unmatched_g1_vertex_invariant = invariant1(*iter); multimap_iter matching_invariant = unmatched_invariants.find(unmatched_g1_vertex_invariant); BOOST_ASSERT(matching_invariant != unmatched_invariants.end()); f[*iter] = matching_invariant->second; @@ -546,11 +581,17 @@ bool isomorphism(const Graph1& G1, const Graph2& G2, IsoMapping f, typedef typename graph_traits< Graph2 >::vertex_descriptor vertex2_t; typedef typename graph_traits< Graph1 >::vertices_size_type size_type; + typedef typename Invariant1::result_type invariant1_t; + typedef typename Invariant2::result_type invariant2_t; + + BOOST_STATIC_ASSERT(is_same::value); + BOOST_CONCEPT_ASSERT((detail::InvariantConcept)); + // Vertex invariant requirement BOOST_CONCEPT_ASSERT( - (AdaptableUnaryFunctionConcept< Invariant1, size_type, vertex1_t >)); + (AdaptableUnaryFunctionConcept< Invariant1, invariant1_t, vertex1_t >)); BOOST_CONCEPT_ASSERT( - (AdaptableUnaryFunctionConcept< Invariant2, size_type, vertex2_t >)); + (AdaptableUnaryFunctionConcept< Invariant2, invariant2_t, vertex2_t >)); // Property map requirements BOOST_CONCEPT_ASSERT(