简体   繁体   中英

Boost.Graph library: how to use boost::is_isomorphism with named vertices

This Issue is similar to BGL: Example of isomorphism with vertex invariants

I am working on a Boost.Graph tutorial and calling boost::is_isomorphism on two graphs without properties is easy. But I cannot get it working when the vertices now have names.

This code shows:

  • How I create path graphs with named vertices (unimportant)
  • My test code
  • My function to test for isomorphism with named vertice

Here is how I create path graphs with named vertices (which is rather unimportant, but shown for completion):

boost::adjacency_list<
  boost::vecS,
  boost::vecS,
  boost::undirectedS,
  boost::property<
    boost::vertex_name_t, std::string
  >
>
create_named_vertices_path_graph(
  const std::vector<std::string>& names
) noexcept
{
  auto g = create_empty_undirected_named_vertices_graph();
  if (names.size() == 0) { return g; }

  auto vertex_name_map
    = get( //not boost::get
      boost::vertex_name,
      g
    );

  auto vd_1 = boost::add_vertex(g);
  vertex_name_map[vd_1] = *names.begin();
  if (names.size() == 1) return g;

  const auto j = std::end(names);
  auto i = std::begin(names);
  for (++i; i!=j; ++i) //Skip first
  {
    auto vd_2 = boost::add_vertex(g);
    vertex_name_map[vd_2] = *i;
    const auto aer = boost::add_edge(vd_1, vd_2, g);
    assert(aer.second);
    vd_1 = vd_2;
  }
  return g;
}

Here is my test:

void is_named_vertices_isomorphic_demo() noexcept
{
  const auto g = create_named_vertices_path_graph(
    { "Alpha", "Beta", "Gamma" }
  );
  const auto h = create_named_vertices_path_graph(
    { "Alpha", "Gamma", "Beta" }
  );
  assert( is_named_vertices_isomorphic(g,g));
  assert(!is_named_vertices_isomorphic(g,h));
}

I would like to write the function is_named_vertices_isomorphic more or less as such (note: this will compile, but fail the test, as inspired by BGL: Example of isomorphism with vertex invariants ):

template <typename graph1, typename graph2>
bool is_named_vertices_isomorphic_correct(
  const graph1& g,
  const graph2& h
) noexcept
{
  auto ref_index_map = get(boost::vertex_index, g);
  using vd = typename boost::graph_traits<graph1>::vertex_descriptor;
  std::vector<vd> iso(boost::num_vertices(g));
  return boost::isomorphism(g,h,
    boost::isomorphism_map(
      make_iterator_property_map(iso.begin(), ref_index_map, iso[0])
    )
  );
}

Looking at the question BGL: Example of isomorphism with vertex invariants made me come up with this:

template <typename Graph>
std::string discrete_vertex_invariant(
  const typename boost::graph_traits<Graph>::vertex_descriptor& vd,
  const Graph &g
)
{
  const auto name_map = get(boost::vertex_name,g);
  return name_map[vd];
}

template <typename Graph>
class discrete_vertex_invariant_functor
{
    using vertex_t = typename boost::graph_traits<Graph>::vertex_descriptor;
    const Graph& m_graph;
public:
    using result_type = std::string;
    using argument_type = vertex_t;
    discrete_vertex_invariant_functor(const Graph &g) : m_graph(g) {}
    result_type operator()(const vertex_t& vd) const
    {
        return discrete_vertex_invariant(vd,m_graph);
    }
    result_type max() const
    {
        return "";
    }
};

//helper function to help with argument deduction
template <typename Graph>
discrete_vertex_invariant_functor<Graph> make_discrete_vertex_invariant(
  const Graph &g
)
{
  return discrete_vertex_invariant_functor<Graph>(g);
}

template <typename graph1, typename graph2>
bool is_named_vertices_isomorphic_correct(
  const graph1& g,
  const graph2& h
) noexcept
{
  auto ref_index_map = get(boost::vertex_index, g);
  using vd = typename boost::graph_traits<graph1>::vertex_descriptor;
  std::vector<vd> iso(boost::num_vertices(g));
  return boost::isomorphism(
    g,
    h,
    isomorphism_map(
      make_iterator_property_map(iso.begin(), ref_index_map, iso[0])
    ).vertex_invariant1(make_discrete_vertex_invariant(g))
     .vertex_invariant2(make_discrete_vertex_invariant(h))
  );
}

Both solutions fail. Who can help me out?

It does seem you're not after iso-morphism, at least not strictly according to the definition.

If you actually wished to compare graphs regardless of the Vertex Index ordering, but strictly comparing vertex names, you will have to map the names onto integral types (because the max_vertex_invariant value is expected to be unsigned integral).

Here's a simplistic way to achieve it:

template <typename graph1, typename graph2>
bool is_named_vertices_isomorphic/*_correct*/(const graph1 &g, const graph2 &h) noexcept {
    auto ref_index_map = get(boost::vertex_index, g);
    using vd = typename boost::graph_traits<graph1>::vertex_descriptor;
    std::vector<vd> iso(boost::num_vertices(g));

    VertexInvariant::Map shared_names;
    VertexInvariant inv1 { g, shared_names };
    VertexInvariant inv2 { h, shared_names };

    inv1.collect_names();
    inv2.collect_names();

    return boost::isomorphism(g, h,
            boost::isomorphism_map(make_iterator_property_map(iso.begin(), ref_index_map))
            .vertex_invariant1(inv1)
            .vertex_invariant2(inv2)
        );
}

I've not made things generic yet, because it distracts.

For good form, you'd need to use boost::property_maps::property_traits<boost::property_map<graph1, boost::vertex_name_t>::const_type>::value_type or similar, and check that the resultant type is actually compatible for comparison between graph1 and graph1 etc.

To be "actually" generic you'd want to pass in a deduced property map instead of boost::get(boost::vertex_name, g) and so on.

To be honest, I feel that this doesn't make much sense for such semantic-laden things as the iso-morphism invariants. You can obviously make things as generic as you need them to be for your application.

struct VertexInvariant {
    using Map = std::map<std::string, size_t>;
    Graph const& _graph;
    Map&         _mappings;

    using result_type = size_t;
    using argument_type = Graph::vertex_descriptor;

    size_t operator()(argument_type u) const {
        return _mappings.at(boost::get(boost::vertex_name, _graph, u));
    }
    size_t max() const { return _mappings.size(); }

    void collect_names() {
        for (auto vd : boost::make_iterator_range(boost::vertices(_graph))) {
            size_t next_id = _mappings.size();
            auto ins = _mappings.insert({ boost::get(boost::vertex_name, _graph, vd), next_id});
            if (ins.second) {
                //std::cout << "Mapped '" << ins.first->first << "' to " << ins.first->second << "\n";
            }
        }
    }
};

Where the helper VertexInvariant is defined as:

struct VertexInvariant {
    using Map = std::map<std::string, size_t>;
    Graph const& _graph;
    Map&         _mappings;

    using result_type = size_t;
    using argument_type = Graph::vertex_descriptor;

    size_t operator()(argument_type u) const {
        return _mappings.at(boost::get(boost::vertex_name, _graph, u));
    }
    size_t max() const { return _mappings.size(); }

    void collect_names() {
        for (auto vd : boost::make_iterator_range(boost::vertices(_graph))) {
            size_t next_id = _mappings.size();
            auto ins = _mappings.insert({ boost::get(boost::vertex_name, _graph, vd), next_id});
            if (ins.second) {
                //std::cout << "Mapped '" << ins.first->first << "' to " << ins.first->second << "\n";
            }
        }
    }
};

Full Demo

Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/isomorphism.hpp>
#include <boost/graph/vf2_sub_graph_iso.hpp>
#include <boost/graph/graph_utility.hpp>

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
                                    boost::property<boost::vertex_name_t, std::string> >;

Graph create_empty_undirected_named_vertices_graph() { return {}; }

Graph create_named_vertices_path_graph(const std::vector<std::string> &names) noexcept {
    auto g = create_empty_undirected_named_vertices_graph();
    if (names.size() == 0) {
        return g;
    }

    auto vertex_name_map = get(boost::vertex_name, g); // not boost::get

    auto vd_1 = boost::add_vertex(g);
    vertex_name_map[vd_1] = *names.begin();
    if (names.size() == 1)
        return g;

    const auto j = std::end(names);
    auto name_it = std::begin(names);
    for (++name_it; name_it != j; ++name_it) // Skip first
    {
        auto vd_2 = boost::add_vertex(g);
        vertex_name_map[vd_2] = *name_it;
        const auto aer = boost::add_edge(vd_1, vd_2, g);
        assert(aer.second);
        vd_1 = vd_2;
    }
    return g;
}

//////////////////////////////////////// {{{
namespace {

    struct VertexInvariant {
        using Map = std::map<std::string, size_t>;
        Graph const& _graph;
        Map&         _mappings;

        using result_type = size_t;
        using argument_type = Graph::vertex_descriptor;

        size_t operator()(argument_type u) const {
            return _mappings.at(boost::get(boost::vertex_name, _graph, u));
        }
        size_t max() const { return _mappings.size(); }

        void collect_names() {
            for (auto vd : boost::make_iterator_range(boost::vertices(_graph))) {
                size_t next_id = _mappings.size();
                auto ins = _mappings.insert({ boost::get(boost::vertex_name, _graph, vd), next_id});
                if (ins.second) {
                    //std::cout << "Mapped '" << ins.first->first << "' to " << ins.first->second << "\n";
                }
            }
        }
    };


}
//////////////////////////////////////// }}}

template <typename graph1, typename graph2>
bool is_named_vertices_isomorphic/*_correct*/(const graph1 &g, const graph2 &h) noexcept {
    auto ref_index_map = get(boost::vertex_index, g);
    using vd = typename boost::graph_traits<graph1>::vertex_descriptor;
    std::vector<vd> iso(boost::num_vertices(g));

    VertexInvariant::Map shared_names;
    VertexInvariant inv1 { g, shared_names };
    VertexInvariant inv2 { h, shared_names };

    inv1.collect_names();
    inv2.collect_names();

    return boost::isomorphism(g, h,
            boost::isomorphism_map(make_iterator_property_map(iso.begin(), ref_index_map))
            .vertex_invariant1(inv1)
            .vertex_invariant2(inv2)
        );
}

void is_named_vertices_isomorphic_demo() noexcept {
    const auto g = create_named_vertices_path_graph({ "Alpha", "Beta", "Gamma" });
    std::cout << "\n==== g:\n"; boost::print_graph(g, boost::get(boost::vertex_name, g));
    const auto h = create_named_vertices_path_graph({ "Alpha", "Gamma", "Beta" });
    std::cout << "\n==== h:\n"; boost::print_graph(h, boost::get(boost::vertex_name, h));

    assert(is_named_vertices_isomorphic(g, g));
    assert(!is_named_vertices_isomorphic(g, h));
}

int main() { is_named_vertices_isomorphic_demo(); }

Prints:

==== g:
Alpha --> Beta 
Beta --> Gamma 
Gamma --> 

==== h:
Alpha --> Gamma 
Gamma --> Beta 
Beta --> 

The function, without the named_vertex_invariant class:

#include "named_vertex_invariant.h"

#include <boost/graph/vf2_sub_graph_iso.hpp>
#include <boost/graph/graph_utility.hpp>

template <typename graph>
bool is_named_vertices_isomorphic(
  const graph &g,
  const graph &h
) noexcept {
  using vd = typename boost::graph_traits<graph>::vertex_descriptor;

  auto vertex_index_map = get(boost::vertex_index, g);
  std::vector<vd> iso(boost::num_vertices(g));

  typename named_vertex_invariant<graph>::str_to_int_map shared_names;
  named_vertex_invariant<graph> inv1{g, shared_names};
  named_vertex_invariant<graph> inv2{h, shared_names};
  inv1.collect_names();
  inv2.collect_names();

  return boost::isomorphism(g, h,
    boost::isomorphism_map(
      make_iterator_property_map(
        iso.begin(),
        vertex_index_map
      )
    )
    .vertex_invariant1(inv1)
    .vertex_invariant2(inv2)
  );
}

The named_vertex_invariant class:

#include <map>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/isomorphism.hpp>

template <class graph>
struct named_vertex_invariant {
  using str_to_int_map = std::map<std::string, size_t>;
  using result_type = size_t;
  using argument_type
    = typename boost::graph_traits<graph>::vertex_descriptor;

  const graph& m_graph;
  str_to_int_map& m_mappings;

  size_t operator()(argument_type u) const {
      return m_mappings.at(boost::get(boost::vertex_name, m_graph, u));
  }
  size_t max() const noexcept { return m_mappings.size(); }

  void collect_names() noexcept {
    for (auto vd : boost::make_iterator_range(boost::vertices(m_graph))) {
      size_t next_id = m_mappings.size();
      auto ins = m_mappings.insert(
        { boost::get(boost::vertex_name, m_graph, vd), next_id}
      );
      if (ins.second) {
      //  std::cout << "Mapped '" << ins.first->first << "' to " << ins.first->second << "\n";
      }
    }
  }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM