Commit ed413845 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Implement subgraph_isomorphism()

Support for edge/vertex labelling is 99% done, but it is currently
impractical to compile with GCC. If GCC 4.5 doesn't do a better job,
then a workaround would be necessary.
parent 9078afc3
......@@ -17,6 +17,7 @@ libgraph_tool_topology_la_LDFLAGS = $(MOD_LDFLAGS)
libgraph_tool_topology_la_SOURCES = \
graph_topology.cc \
graph_isomorphism.cc \
graph_subgraph_isomorphism.cc \
graph_minimum_spanning_tree.cc \
graph_dominator_tree.cc \
graph_topological_sort.cc \
......@@ -28,4 +29,5 @@ libgraph_tool_topology_la_SOURCES = \
libgraph_tool_topology_la_include_HEADERS = \
graph_components.hh
graph_components.hh \
graph_subgraph_isomorphism.hh
\ No newline at end of file
// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2010 Tiago de Paula Peixoto <tiago@forked.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "graph.hh"
#include "graph_filtering.hh"
#include <graph_subgraph_isomorphism.hh>
#include <graph_python_interface.hh>
using namespace graph_tool;
using namespace boost;
template <class Graph1, class Graph2, class Label1, class Label2>
class PropLabelling
{
public:
PropLabelling(const Graph1& g1, const Graph2& g2,
Label1 label1, Label2 label2)
: _g1(g1), _g2(g2), _label1(label1), _label2(label2) {}
typedef typename property_traits<Label1>::value_type value_type1;
typedef typename property_traits<Label2>::value_type value_type2;
bool operator()(typename graph_traits<Graph1>::vertex_descriptor v1,
typename graph_traits<Graph1>::vertex_descriptor v2) const
{
if (in_degreeS()(v2, _g2) < in_degreeS()(v1, _g1) ||
out_degree(v2, _g2) < out_degree(v1, _g1))
return false;
return _label1[v1] == _label2[v2];
}
bool operator()(typename graph_traits<Graph1>::edge_descriptor e1,
typename graph_traits<Graph1>::edge_descriptor e2) const
{
return _label1[e1] == _label2[e2];
}
private:
const Graph1& _g1;
const Graph2& _g2;
Label1 _label1;
Label2 _label2;
};
struct get_subgraphs
{
template <class Graph1, class Graph2, class VertexLabel,
class EdgeLabel>
void operator()(const Graph1& g1, const Graph2* g2,
VertexLabel vertex_label1, boost::any vertex_label2,
EdgeLabel edge_label1, boost::any edge_label2,
vector<vector<pair<size_t,size_t> > >& F) const
{
typedef PropLabelling<Graph1,Graph2,VertexLabel,VertexLabel>
vlabelling_t;
typedef PropLabelling<Graph1,Graph2,EdgeLabel,EdgeLabel>
elabelling_t;
subgraph_isomorphism
(g1, *g2, vlabelling_t(g1, *g2, vertex_label1,
any_cast<VertexLabel>(vertex_label2)),
elabelling_t(g1, *g2, edge_label1,
any_cast<EdgeLabel>(edge_label2)), F);
}
};
struct get_mapping
{
template <class Graph1, class Graph2, class EdgeLabel, class VertexMap,
class EdgeMap, class EdgeIndexMap>
void operator()(const Graph1& g1, const Graph2* g2, EdgeLabel edge_label1,
boost::any edge_label2, vector<pair<size_t, size_t> >& F,
VertexMap vmapping, EdgeMap emapping,
EdgeIndexMap edge_index2) const
{
typedef PropLabelling<Graph1,Graph2,EdgeLabel,EdgeLabel>
elabelling_t;
elabelling_t edge_labelling(g1, *g2, edge_label1,
any_cast<EdgeLabel>(edge_label2));
int i, N = F.size();
#pragma omp parallel for default(shared) private(i) schedule(dynamic)
for (i = 0; i < N; ++i)
{
if (vertex(i, g1) == graph_traits<Graph1>::null_vertex())
continue;
vmapping[vertex(F[i].first, g1)] = vertex(F[i].second, *g2);
typename graph_traits<Graph1>::out_edge_iterator e, e_end;
for (tie(e, e_end) = out_edges(vertex(i, g1), g1); e != e_end; ++e)
{
bool found = false;
typename graph_traits<Graph2>::out_edge_iterator e2, e2_end;
for (tie(e2, e2_end) = out_edges(vertex(F[i].second, *g2), *g2);
e2 != e2_end; ++e2)
{
if (target(*e2, *g2) ==
vertex(F[target(*e, g1)].second, *g2) &&
edge_labelling(*e, *e2))
{
emapping[*e] = edge_index2[*e2];
found = true;
}
}
if (!found)
throw GraphException("edge not found... "
"can't be isomorphism!!! "
"This is a bug.");
}
}
}
};
struct directed_graph_view_pointers:
mpl::transform<graph_tool::detail::always_directed,
mpl::quote1<add_pointer> >::type {};
struct undirected_graph_view_pointers:
mpl::transform<graph_tool::detail::never_directed,
mpl::quote1<add_pointer> >::type {};
// typedef mpl::push_back<vertex_properties,
// ConstantPropertyMap<bool,GraphInterface::vertex_t> >
// ::type vertex_props_t;
// typedef mpl::push_back<edge_properties,
// ConstantPropertyMap<bool,GraphInterface::edge_t> >
// ::type edge_props_t;
typedef mpl::vector<ConstantPropertyMap<bool,GraphInterface::vertex_t> > vertex_props_t;
typedef mpl::vector<ConstantPropertyMap<bool,GraphInterface::edge_t> > edge_props_t;
void subgraph_isomorphism(GraphInterface& gi1, GraphInterface& gi2,
boost::any vertex_label1, boost::any vertex_label2,
boost::any edge_label1, boost::any edge_label2,
python::list vmapping, python::list emapping)
{
if (gi1.GetDirected() != gi2.GetDirected())
return;
if (vertex_label1.empty())
{
vertex_label1 = vertex_label2 =
ConstantPropertyMap<bool,GraphInterface::vertex_t>(true);
}
if (edge_label1.empty())
{
edge_label1 = edge_label2 =
ConstantPropertyMap<bool,GraphInterface::edge_t>(true);
}
vector<vector<pair<size_t,size_t> > > F;
if (gi1.GetDirected())
{
run_action<graph_tool::detail::always_directed>()
(gi1, bind<void>(get_subgraphs(),
_1, _2, _3, vertex_label2, _4, edge_label2,
ref(F)),
directed_graph_view_pointers(), vertex_props_t(),
edge_props_t())
(gi2.GetGraphView(), vertex_label1, edge_label1);
}
else
{
run_action<graph_tool::detail::never_directed>()
(gi1, bind<void>(get_subgraphs(),
_1, _2, _3, vertex_label2, _4, edge_label2,
ref(F)),
undirected_graph_view_pointers(), vertex_props_t(),
edge_props_t())
(gi2.GetGraphView(), vertex_label1, edge_label1);
}
for (size_t i = 0; i < F.size(); ++i)
{
typedef property_map_type
::apply<int64_t, GraphInterface::vertex_index_map_t>::type
vmap_t;
typedef property_map_type
::apply<int64_t, GraphInterface::edge_index_map_t>::type
emap_t;
vmap_t::unchecked_t vm(gi1.GetVertexIndex(), gi1.GetNumberOfVertices());
emap_t::unchecked_t ep(gi1.GetEdgeIndex(), gi1.GetMaxEdgeIndex()+1);
if (gi1.GetDirected())
{
run_action<graph_tool::detail::always_directed>()
(gi1, bind<void>(get_mapping(),
_1, _2, _3, edge_label2,
ref(F[i]), ref(vm), ref(ep),
gi2.GetEdgeIndex()),
directed_graph_view_pointers(), edge_props_t())
(gi2.GetGraphView(), edge_label1);
}
else
{
run_action<graph_tool::detail::never_directed>()
(gi1, bind<void>(get_mapping(),
_1, _2, _3, edge_label2,
ref(F[i]), ref(vm), ref(ep),
gi2.GetEdgeIndex()),
undirected_graph_view_pointers(), edge_props_t())
(gi2.GetGraphView(), edge_label1);
}
vmapping.append(PythonPropertyMap<vmap_t>(vm.get_checked()));
emapping.append(PythonPropertyMap<emap_t>(ep.get_checked()));
}
}
// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2010 Tiago de Paula Peixoto <tiago@forked.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef SUBGRAPH_ISOMORPHISM_HPP
#define SUBGRAPH_ISOMORPHISM_HPP
#include <boost/graph/graph_traits.hpp>
#include <utility>
#include <tr1/unordered_set>
namespace boost
{
using namespace std;
namespace detail {
//sparse matrix
typedef vector<tr1::unordered_set<size_t> > matrix_t;
struct check_adjacency
{
template <class Graph>
typename graph_traits<Graph>::vertex_descriptor
get_vertex(const typename graph_traits<Graph>::edge_descriptor& e, Graph& g,
mpl::true_ out_edges)
{
return target(e, g);
}
template <class Graph>
typename graph_traits<Graph>::vertex_descriptor
get_vertex(const typename graph_traits<Graph>::edge_descriptor& e, Graph& g,
mpl::false_ out_edges)
{
return source(e, g);
}
template <class Graph, class IsOut>
struct get_edge_iterator
{
typedef typename graph_traits<Graph>::out_edge_iterator type;
static pair<type,type>
edges(typename graph_traits<Graph>::vertex_descriptor v, Graph& g)
{
return out_edges(v, g);
}
};
template <class Graph>
struct get_edge_iterator<Graph, mpl::false_>
{
typedef typename graph_traits<Graph>::in_edge_iterator type;
static pair<type,type>
edges(typename graph_traits<Graph>::vertex_descriptor v, Graph& g)
{
return in_edges(v, g);
}
};
template <class Graph1, class Graph2, class EdgeLabelling>
bool operator()(typename graph_traits<Graph1>::vertex_descriptor k,
typename graph_traits<Graph2>::vertex_descriptor l,
matrix_t& M, EdgeLabelling& edge_labelling,
Graph1& g1, Graph2& g2, mpl::true_ directed)
{
return do_check(k, l, M, edge_labelling, g1, g2, mpl::true_()) &&
do_check(k, l, M, edge_labelling, g1, g2, mpl::false_());
}
template <class Graph1, class Graph2, class EdgeLabelling>
bool operator()(typename graph_traits<Graph1>::vertex_descriptor k,
typename graph_traits<Graph2>::vertex_descriptor l,
matrix_t& M, EdgeLabelling& edge_labelling,
Graph1& g1, Graph2& g2, mpl::false_ directed)
{
return do_check(k, l, M, edge_labelling, g1, g2, mpl::true_());
}
template <class Graph1, class Graph2, class EdgeLabelling, class IsOut>
bool do_check(typename graph_traits<Graph1>::vertex_descriptor k,
typename graph_traits<Graph2>::vertex_descriptor l,
matrix_t& M, EdgeLabelling& edge_labelling, Graph1& g1,
Graph2& g2, IsOut)
{
bool valid = true;
typename get_edge_iterator<Graph1, IsOut>::type e1, e1_end;
for (tie(e1, e1_end) =
get_edge_iterator<Graph1, IsOut>::edges(k, g1);
e1 != e1_end; ++e1)
{
typename graph_traits<Graph1>::vertex_descriptor v1 =
get_vertex(*e1, g1, IsOut());
bool is_adjacent = false;
typename get_edge_iterator<Graph2, IsOut>::type e2, e2_end;
for (tie(e2, e2_end) =
get_edge_iterator<Graph2, IsOut>::edges(l, g2);
e2 != e2_end; ++e2)
{
typename graph_traits<Graph2>::vertex_descriptor v2 =
get_vertex(*e2, g2, IsOut());
if (M[v1].find(v2) != M[v1].end() && edge_labelling(*e1, *e2))
{
is_adjacent = true;
break;
}
}
if (!is_adjacent)
{
valid = false;
break;
}
}
return valid;
}
};
template <class Graph1, class Graph2, class EdgeLabelling>
bool refine_check(const Graph1& g1, const Graph2& g2, matrix_t& M, size_t count,
tr1::unordered_set<size_t>& already_mapped,
EdgeLabelling edge_labelling)
{
matrix_t M_temp(num_vertices(g1));
int k = 0, N = num_vertices(g1);
#pragma omp parallel for default(shared) private(k) schedule(dynamic)
for (k = 0; k < int(count); ++k)
M_temp[k] = M[k];
size_t n_mod = 1;
while (n_mod > 0)
{
n_mod = 0;
bool abort = false;
#pragma omp parallel for default(shared) private(k) schedule(dynamic) \
reduction(+:n_mod)
for (k = count; k < N; ++k)
{
if (abort)
continue;
if (vertex(k, g1) == graph_traits<Graph1>::null_vertex())
continue;
tr1::unordered_set<size_t> m_new;
for (typeof(M[k].begin()) li = M[k].begin(); li != M[k].end(); ++li)
{
size_t l = *li;
if (already_mapped.find(l) != already_mapped.end())
continue;
bool valid = check_adjacency()
(vertex(k, g1), vertex(l, g2), M, edge_labelling, g1, g2,
typename is_directed::apply<Graph1>::type());
if (valid)
m_new.insert(l);
}
if (m_new.empty())
{
abort = true;
continue;
}
M_temp[k].swap(m_new);
if (M_temp[k].size() < M[k].size())
n_mod++;
}
if (abort)
return false;
M.swap(M_temp);
}
return true;
}
template <class Graph1, class Graph2, class EdgeLabelling, class Mapping>
void find_mappings(const Graph1& g1, const Graph2& g2, matrix_t M0,
vector<Mapping>& FF, EdgeLabelling edge_labelling)
{
size_t i = 0;
for (i=0; i < num_vertices(g1); ++i)
if (vertex(i, g1) != graph_traits<Graph1>::null_vertex())
break;
int last_i = 0;
for (last_i = num_vertices(g1)-1; last_i >= 0; --last_i)
if (vertex(i, g1) != graph_traits<Graph1>::null_vertex())
break;
for (; i < num_vertices(g2); ++i)
if (vertex(i, g2) != graph_traits<Graph2>::null_vertex())
break;
Mapping F;
list<tuple<matrix_t, size_t,
typename matrix_t::value_type::const_iterator> > Mstack;
Mstack.push_back(make_tuple(M0,i,M0[i].begin()));
get<2>(Mstack.back()) = get<0>(Mstack.back())[i].begin();
tr1::unordered_set<size_t> already_mapped;
// perform depth-first search of combination space
while (!Mstack.empty())
{
const matrix_t& M = get<0>(Mstack.back());
size_t& i = get<1>(Mstack.back());
typename matrix_t::value_type::const_iterator& mi =
get<2>(Mstack.back());
if (mi == M[i].end())
{
// dead end
Mstack.pop_back();
if (!F.empty())
{
already_mapped.erase(F.back().second);
F.pop_back();
}
continue;
}
matrix_t M_prime(M);
M_prime[i].clear();
M_prime[i].insert(*mi);
already_mapped.insert(*mi);
size_t c_mi = *mi;
// move locally to next child
++mi;
size_t ni = i + 1;
for (; ni < num_vertices(g1); ++ni)
if (vertex(ni, g1) != graph_traits<Graph1>::null_vertex())
break;
// refine search tree
if (refine_check(g1, g2, M_prime, ni, already_mapped, edge_labelling))
{
// store the current mapping so far
F.push_back(std::make_pair(i, c_mi));
if (ni < size_t(last_i))
{
// proceed with search at a higher depth
Mstack.push_back(make_tuple(M_prime, ni, M_prime[ni].begin()));
get<2>(Mstack.back()) = get<0>(Mstack.back())[ni].begin();
}
else
{
// maximum depth reached: visit all end leafs
for (typeof(M_prime[ni].begin()) iter = M_prime[ni].begin();
iter != M_prime[ni].end(); ++iter)
{
F.push_back(std::make_pair(ni, *iter));
FF.push_back(F);
F.pop_back();
}
// we are done which this tree node
mi = M[i].end();
F.pop_back();
already_mapped.erase(c_mi);
}
}
else
{
already_mapped.erase(c_mi);
}
}
}
} // namespace detail
template <class Graph1, class Graph2, class VertexLabelling,
class EdgeLabelling, class Mapping>
void subgraph_isomorphism(const Graph1& g1, const Graph2& g2,
VertexLabelling vertex_labelling,
EdgeLabelling edge_labelling, vector<Mapping>& F)
{
detail::matrix_t M0(num_vertices(g1));
bool abort = false;
int i, N = num_vertices(g1);
#pragma omp parallel for default(shared) private(i) schedule(dynamic)
for (i = 0; i < N; ++i)
{
if (vertex(i, g1) == graph_traits<Graph1>::null_vertex() || abort)
continue;
for (size_t j = 0; j < num_vertices(g2); ++j)
{
if (vertex(j, g2) == graph_traits<Graph1>::null_vertex())
continue;
if (vertex_labelling(vertex(i,g1), vertex(j,g2)))
M0[i].insert(j);
}
if (M0[i].empty())
abort = true;
}
if (abort)
return;
detail::find_mappings(g1, g2, M0, F, edge_labelling);
}
} // namespace boost
#endif // SUBGRAPH_ISOMORPHISM_HPP
......@@ -32,6 +32,10 @@ void topological_sort(GraphInterface& gi, vector<int32_t>& sort);
void dominator_tree(GraphInterface& gi, size_t entry, boost::any pred_map);
void transitive_closure(GraphInterface& gi, GraphInterface& tcgi);
bool is_planar(GraphInterface& gi, boost::any embed_map, boost::any kur_map);
void subgraph_isomorphism(GraphInterface& gi1, GraphInterface& gi2,
boost::any vertex_label1, boost::any vertex_label2,
boost::any edge_label1, boost::any edge_label2,
python::list vmapping, python::list emapping);
void export_components();
void export_dists();
......@@ -40,6 +44,7 @@ void export_all_dists();
BOOST_PYTHON_MODULE(libgraph_tool_topology)
{
def("check_isomorphism", &check_isomorphism);
def("subgraph_isomorphism", &subgraph_isomorphism);
def("get_kruskal_spanning_tree", &get_kruskal_spanning_tree);
def("get_prim_spanning_tree", &get_prim_spanning_tree);
def("topological_sort", &topological_sort);