2
0
mirror of https://github.com/boostorg/graph.git synced 2026-01-19 04:12:11 +00:00

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
This commit is contained in:
Jan-Grimo Sobez
2023-09-07 19:25:47 +02:00
parent 4375631fa6
commit 5ec4327816
2 changed files with 82 additions and 42 deletions

View File

@@ -114,9 +114,8 @@ equal) or to impose extra conditions on the result. The
href="http://www.boost.org/sgi/stl/AdaptableUnaryFunction.html">AdaptableUnaryFunction</a>,
with the argument type of <tt>vertex_invariant1</tt> being <tt>Graph1</tt>'s vertex
descriptor type, the argument type of <tt>vertex_invariant2</tt> being
<tt>Graph2</tt>'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.
<tt>Graph2</tt>'s vertex descriptor type, and both functions sharing a
result type that is totally ordered and hashable, such as an integer.
<br>
<b>Default:</b> <tt>degree_vertex_invariant</tt> for both arguments<br>
<b>Python</b>: Unsupported parameter.

View File

@@ -30,6 +30,22 @@ namespace boost
namespace detail
{
template < typename T >
struct HashableConcept {
BOOST_CONCEPT_USAGE(HashableConcept)
{
hash<T> hasher;
typedef typename hash<T>::result_type hash_result;
hash_result val = hasher(t);
boost::ignore_unused_variable_warning(val);
}
T t;
};
template < typename Invariant >
struct InvariantConcept : LessThanComparable<Invariant>, EqualityComparable<Invariant>, HashableConcept<Invariant> {};
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<std::size_t>::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<invariant1_t, invariant2_t>::value);
BOOST_CONCEPT_ASSERT((detail::InvariantConcept<invariant1_t>));
// 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(