Skip to content

Commit

Permalink
Merge pull request #349 from jan-grimo/isomorphism-no-contiguous-inva…
Browse files Browse the repository at this point in the history
…riants

Isomorphism: Remove invariant contiguous range requirement
  • Loading branch information
jeremy-murphy authored Apr 15, 2024
2 parents 4375631 + 044c7d6 commit 6caa0ba
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 48 deletions.
5 changes: 2 additions & 3 deletions doc/isomorphism.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,8 @@ <h3>Named Parameters</h3>
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.
Expand Down
117 changes: 72 additions & 45 deletions include/boost/graph/isomorphism.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <boost/graph/depth_first_search.hpp>
#include <boost/detail/algorithm.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/pending/indirect_cmp.hpp> // for make_indirect_pmap
#include <boost/concept/assert.hpp>

Expand All @@ -32,15 +33,14 @@ namespace detail

template < typename Graph1, typename Graph2, typename IsoMapping,
typename Invariant1, typename Invariant2, typename IndexMap1,
typename IndexMap2 >
typename IndexMap2, typename InvariantCountMap = boost::unordered_flat_map<typename Invariant1::result_type, typename graph_traits< Graph1 >::vertices_size_type > >
class isomorphism_algo
{
typedef typename graph_traits< Graph1 >::vertex_descriptor vertex1_t;
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;

const Graph1& G1;
const Graph2& G2;
Expand Down Expand Up @@ -81,17 +81,20 @@ namespace detail
friend struct compare_multiplicity;
struct compare_multiplicity
{
compare_multiplicity(Invariant1 invariant1, size_type* multiplicity)
: invariant1(invariant1), multiplicity(multiplicity)
compare_multiplicity(Invariant1 invariant1, const InvariantCountMap& multiplicity)
: invariant1(invariant1), multiplicity(&multiplicity)
{
}
bool operator()(const vertex1_t& x, const vertex1_t& y) const
{
return multiplicity[invariant1(x)]
< multiplicity[invariant1(y)];
auto x_multiplicity_iter = multiplicity->find(invariant1(x));
assert(x_multiplicity_iter != multiplicity->end());
auto y_multiplicity_iter = multiplicity->find(invariant1(y));
assert(y_multiplicity_iter != multiplicity->end());
return *x_multiplicity_iter < *y_multiplicity_iter;
}
Invariant1 invariant1;
size_type* multiplicity;
const InvariantCountMap* multiplicity;
};

struct record_dfs_order : default_dfs_visitor
Expand Down Expand Up @@ -158,46 +161,63 @@ namespace detail
);
}

// Generates map of invariant multiplicity from sorted invariants
template<typename ForwardIterator>
InvariantCountMap multiplicities(ForwardIterator first, const ForwardIterator last)
{
typedef typename InvariantCountMap::iterator invar_map_iter;

assert(std::is_sorted(first, last));
InvariantCountMap invar_multiplicity;

if(first == last)
return invar_multiplicity;

invariant_t invar = *first;
invar_map_iter inserted = invar_multiplicity.emplace(invar, 1).first;
++first;
for(; first != last; ++first)
{
if(*first == invar)
{
inserted->second += 1;
}
else
{
invar = *first;
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);

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;
}

// 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< 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.begin(), invar1_array.end())));

std::vector< default_color_type > color_vec(num_vertices(G1));
safe_iterator_property_map<
Expand Down Expand Up @@ -436,13 +456,15 @@ namespace detail
}
}

if(!unmatched_g1_vertices.empty()) {
typedef unordered_multimap< invar2_value, vertex2_t > g2_invariant_vertex_multimap;
if(!unmatched_g1_vertices.empty())
{
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)
{
if(!in_S[v]) {
if(!in_S[v])
{
unmatched_invariants.emplace(invariant2(v), v);
}
}
Expand All @@ -451,7 +473,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;
Expand Down Expand Up @@ -546,11 +568,16 @@ 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);

// 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(
Expand Down

0 comments on commit 6caa0ba

Please sign in to comment.