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

Implement "generator" option for subgraph_isomorphism()

This introduces support for returning a generator in
subgraph_isomorphism(), so that all matches do not need to be kept on
memory simultaneously.

This fixes issue #255.
parent 8d57975d
Pipeline #75 failed with stage
......@@ -580,4 +580,11 @@ void export_python_interface()
def("get_vlist", get_vlist);
def("get_elist", get_elist);
#ifdef HAVE_BOOST_COROUTINE
class_<CoroGenerator>("CoroGenerator", no_init)
.def("__iter__", objects::identity_function())
.def("next", &CoroGenerator::next)
.def("__next__", &CoroGenerator::next);
#endif
}
......@@ -46,8 +46,11 @@ namespace std
#include "graph_selectors.hh"
#include "numpy_bind.hh"
#ifdef HAVE_BOOST_COROUTINE
#include <boost/coroutine/all.hpp>
#endif // HAVE_BOOST_COROUTINE
// this file includes a simple python interface for the internally kept
// This file includes a simple python interface for the internally kept
// graph. It defines a PythonVertex, PythonEdge and PythonIterator template
// classes, which contain the proper member functions for graph traversal. These
// types are then specialized for each version of the adapted graph (directed,
......@@ -78,6 +81,34 @@ private:
std::pair<Iterator,Iterator> _e;
};
#ifdef HAVE_BOOST_COROUTINE
// generic coroutine generator adaptor
typedef boost::coroutines::asymmetric_coroutine<boost::python::object> coro_t;
class CoroGenerator
{
public:
template <class Dispatch>
CoroGenerator(Dispatch& dispatch)
: _coro(std::make_shared<coro_t::pull_type>(dispatch)),
_iter(begin(*_coro)), _end(end(*_coro)) {}
boost::python::object next()
{
if (_iter == _end)
boost::python::objects::stop_iteration_error();
boost::python::object oe = *_iter;
++_iter;
return oe;
}
private:
std::shared_ptr<coro_t::pull_type> _coro;
coro_t::pull_type::iterator _iter;
coro_t::pull_type::iterator _end;
};
#endif // HAVE_BOOST_COROUTINE
// forward declaration of PythonEdge
template <class Graph>
......
......@@ -105,8 +105,6 @@ void a_star_search(GraphInterface& g, size_t source, boost::any dist_map,
#ifdef HAVE_BOOST_COROUTINE
typedef boost::coroutines::asymmetric_coroutine<boost::python::object> coro_t;
class AStarGeneratorVisitor : public astar_visitor<>
{
public:
......@@ -126,27 +124,6 @@ private:
coro_t::push_type& _yield;
};
class AStarGenerator
{
public:
template <class Dispatch>
AStarGenerator(Dispatch& dispatch)
: _coro(std::make_shared<coro_t::pull_type>(dispatch)),
_iter(begin(*_coro)), _end(end(*_coro)) {}
boost::python::object next()
{
if (_iter == _end)
boost::python::objects::stop_iteration_error();
boost::python::object oe = *_iter;
++_iter;
return oe;
}
private:
std::shared_ptr<coro_t::pull_type> _coro;
coro_t::pull_type::iterator _iter;
coro_t::pull_type::iterator _end;
};
#endif // HAVE_BOOST_COROUTINE
boost::python::object astar_search_generator(GraphInterface& g,
......@@ -170,7 +147,7 @@ boost::python::object astar_search_generator(GraphInterface& g,
make_pair(zero, inf), h, std::ref(g)),
writable_vertex_properties())(dist_map);
};
return boost::python::object(AStarGenerator(dispatch));
return boost::python::object(CoroGenerator(dispatch));
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
......@@ -195,7 +172,7 @@ boost::python::object astar_search_generator_fast(GraphInterface& g,
writable_vertex_scalar_properties(),
edge_scalar_properties())(dist_map, weight);
};
return boost::python::object(AStarGenerator(dispatch));
return boost::python::object(CoroGenerator(dispatch));
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
......@@ -208,10 +185,4 @@ void export_astar()
def("astar_search", &a_star_search);
def("astar_generator", &astar_search_generator);
def("astar_generator_fast", &astar_search_generator_fast);
#ifdef HAVE_BOOST_COROUTINE
class_<AStarGenerator>("AStarGenerator", no_init)
.def("__iter__", objects::identity_function())
.def("next", &AStarGenerator::next)
.def("__next__", &AStarGenerator::next);
#endif
}
......@@ -125,8 +125,6 @@ void bfs_search(GraphInterface& g, size_t s, python::object vis)
#ifdef HAVE_BOOST_COROUTINE
typedef boost::coroutines::asymmetric_coroutine<boost::python::object> coro_t;
class BFSGeneratorVisitor : public bfs_visitor<>
{
public:
......@@ -146,27 +144,6 @@ private:
coro_t::push_type& _yield;
};
class BFSGenerator
{
public:
template <class Dispatch>
BFSGenerator(Dispatch& dispatch)
: _coro(std::make_shared<coro_t::pull_type>(dispatch)),
_iter(begin(*_coro)), _end(end(*_coro)) {}
boost::python::object next()
{
if (_iter == _end)
boost::python::objects::stop_iteration_error();
boost::python::object oe = *_iter;
++_iter;
return oe;
}
private:
std::shared_ptr<coro_t::pull_type> _coro;
coro_t::pull_type::iterator _iter;
coro_t::pull_type::iterator _end;
};
#endif // HAVE_BOOST_COROUTINE
boost::python::object bfs_search_generator(GraphInterface& g, size_t s)
......@@ -178,7 +155,7 @@ boost::python::object bfs_search_generator(GraphInterface& g, size_t s)
run_action<graph_tool::detail::all_graph_views,mpl::true_>()
(g, std::bind(do_bfs(), placeholders::_1, s, vis))();
};
return boost::python::object(BFSGenerator(dispatch));
return boost::python::object(CoroGenerator(dispatch));
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
......@@ -189,10 +166,4 @@ void export_bfs()
using namespace boost::python;
def("bfs_search", &bfs_search);
def("bfs_search_generator", &bfs_search_generator);
#ifdef HAVE_BOOST_COROUTINE
class_<BFSGenerator>("BFSGenerator", no_init)
.def("__iter__", objects::identity_function())
.def("next", &BFSGenerator::next)
.def("__next__", &BFSGenerator::next);
#endif
}
......@@ -114,8 +114,6 @@ void dfs_search(GraphInterface& g, size_t s, python::object vis)
#ifdef HAVE_BOOST_COROUTINE
typedef boost::coroutines::asymmetric_coroutine<boost::python::object> coro_t;
class DFSGeneratorVisitor : public dfs_visitor<>
{
public:
......@@ -135,27 +133,6 @@ private:
coro_t::push_type& _yield;
};
class DFSGenerator
{
public:
template <class Dispatch>
DFSGenerator(Dispatch& dispatch)
: _coro(std::make_shared<coro_t::pull_type>(dispatch)),
_iter(begin(*_coro)), _end(end(*_coro)) {}
boost::python::object next()
{
if (_iter == _end)
boost::python::objects::stop_iteration_error();
boost::python::object oe = *_iter;
++_iter;
return oe;
}
private:
std::shared_ptr<coro_t::pull_type> _coro;
coro_t::pull_type::iterator _iter;
coro_t::pull_type::iterator _end;
};
#endif // HAVE_BOOST_COROUTINE
......@@ -169,7 +146,7 @@ boost::python::object dfs_search_generator(GraphInterface& g, size_t s)
(g, std::bind(do_dfs(), placeholders::_1,
g.get_vertex_index(), s, vis))();
};
return boost::python::object(DFSGenerator(dispatch));
return boost::python::object(CoroGenerator(dispatch));
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
......@@ -180,10 +157,4 @@ void export_dfs()
using namespace boost::python;
def("dfs_search", &dfs_search);
def("dfs_search_generator", &dfs_search_generator);
#ifdef HAVE_BOOST_COROUTINE
class_<DFSGenerator>("DFSGenerator", no_init)
.def("__iter__", objects::identity_function())
.def("next", &DFSGenerator::next)
.def("__next__", &DFSGenerator::next);
#endif
}
......@@ -184,8 +184,6 @@ void dijkstra_search(GraphInterface& g, size_t source, boost::any dist_map,
#ifdef HAVE_BOOST_COROUTINE
typedef boost::coroutines::asymmetric_coroutine<boost::python::object> coro_t;
class DJKGeneratorVisitor : public dijkstra_visitor<>
{
public:
......@@ -205,27 +203,6 @@ private:
coro_t::push_type& _yield;
};
class DJKGenerator
{
public:
template <class Dispatch>
DJKGenerator(Dispatch& dispatch)
: _coro(std::make_shared<coro_t::pull_type>(dispatch)),
_iter(begin(*_coro)), _end(end(*_coro)) {}
boost::python::object next()
{
if (_iter == _end)
boost::python::objects::stop_iteration_error();
boost::python::object oe = *_iter;
++_iter;
return oe;
}
private:
std::shared_ptr<coro_t::pull_type> _coro;
coro_t::pull_type::iterator _iter;
coro_t::pull_type::iterator _end;
};
#endif // HAVE_BOOST_COROUTINE
boost::python::object dijkstra_search_generator(GraphInterface& g,
......@@ -248,7 +225,7 @@ boost::python::object dijkstra_search_generator(GraphInterface& g,
make_pair(zero, inf)),
writable_vertex_properties())(dist_map);
};
return boost::python::object(DJKGenerator(dispatch));
return boost::python::object(CoroGenerator(dispatch));
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
......@@ -271,7 +248,7 @@ boost::python::object dijkstra_search_generator_fast(GraphInterface& g,
writable_vertex_scalar_properties(),
edge_scalar_properties())(dist_map, weight);
};
return boost::python::object(DJKGenerator(dispatch));
return boost::python::object(CoroGenerator(dispatch));
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
......@@ -283,10 +260,4 @@ void export_dijkstra()
def("dijkstra_search", &dijkstra_search);
def("dijkstra_generator", &dijkstra_search_generator);
def("dijkstra_generator_fast", &dijkstra_search_generator_fast);
#ifdef HAVE_BOOST_COROUTINE
class_<DJKGenerator>("DJKGenerator", no_init)
.def("__iter__", objects::identity_function())
.def("next", &DJKGenerator::next)
.def("__next__", &DJKGenerator::next);
#endif
}
......@@ -15,67 +15,134 @@
// 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_python_interface.hh>
#include "graph.hh"
#include "graph_filtering.hh"
#include "random.hh"
#include <boost/graph/vf2_sub_graph_iso.hpp>
#include <graph_python_interface.hh>
#ifdef HAVE_BOOST_COROUTINE
#include <boost/coroutine/all.hpp>
#endif // HAVE_BOOST_COROUTINE
using namespace graph_tool;
using namespace boost;
template <class Graph1, class Graph2, class VertexMap>
struct get_match
struct ListMatch
{
get_match(const Graph1& sub, const Graph2& g, vector<VertexMap>& vmaps,
size_t max_n) : _sub(sub), _g(g), _vmaps(vmaps), _max_n(max_n)
{}
template <class CorrespondenceMap1To2,
class CorrespondenceMap2To1>
bool operator()(const CorrespondenceMap1To2& f,
const CorrespondenceMap2To1&)
template <class Graph1, class Graph2, class VertexMap>
struct GetMatch
{
VertexMap c_vmap(get(vertex_index, _sub));
auto vmap = c_vmap.get_unchecked(num_vertices(_sub));
for (auto v : vertices_range(_sub))
GetMatch(const Graph1& sub, const Graph2& g, vector<VertexMap>& vmaps,
size_t max_n) : _sub(sub), _g(g), _vmaps(vmaps), _max_n(max_n)
{}
template <class CorrespondenceMap1To2,
class CorrespondenceMap2To1>
bool operator()(const CorrespondenceMap1To2& f,
const CorrespondenceMap2To1&)
{
auto w = f[v];
if (w == graph_traits<Graph2>::null_vertex())
return true;
vmap[v] = w;
VertexMap c_vmap(get(vertex_index, _sub));
auto vmap = c_vmap.get_unchecked(num_vertices(_sub));
for (auto v : vertices_range(_sub))
{
auto w = f[v];
if (w == graph_traits<Graph2>::null_vertex())
return true;
vmap[v] = w;
}
_vmaps.push_back(c_vmap);
if (_max_n > 0 && _vmaps.size() >= _max_n)
return false;
return true;
}
_vmaps.push_back(c_vmap);
if (_max_n > 0 && _vmaps.size() >= _max_n)
return false;
return true;
const Graph1& _sub;
const Graph2& _g;
vector<VertexMap>& _vmaps;
size_t _max_n;
};
template <class Graph1, class Graph2, class VertexMap>
GetMatch<Graph1, Graph2, VertexMap> get_match(const Graph1& sub,
const Graph2& g,
vector<VertexMap>& vmaps,
size_t max_n)
{
return GetMatch<Graph1, Graph2, VertexMap>(sub, g, vmaps, max_n);
}
};
#ifdef HAVE_BOOST_COROUTINE
typedef boost::coroutines::asymmetric_coroutine<boost::python::object> coro_t;
struct GenMatch
{
GenMatch(coro_t::push_type& yield): _yield(yield) {}
template <class Graph1, class Graph2, class VertexMap>
struct GetMatch
{
GetMatch(const Graph1& sub, const Graph2& g,
coro_t::push_type& yield)
: _sub(sub), _g(g), _yield(yield)
{}
const Graph1& _sub;
const Graph2& _g;
template <class CorrespondenceMap1To2,
class CorrespondenceMap2To1>
bool operator()(const CorrespondenceMap1To2& f,
const CorrespondenceMap2To1&)
{
VertexMap c_vmap(get(vertex_index, _sub));
auto vmap = c_vmap.get_unchecked(num_vertices(_sub));
for (auto v : vertices_range(_sub))
{
auto w = f[v];
if (w == graph_traits<Graph2>::null_vertex())
return true;
vmap[v] = w;
}
_yield(boost::python::object(PythonPropertyMap<VertexMap>(c_vmap)));
return true;
}
const Graph1& _sub;
const Graph2& _g;
coro_t::push_type& _yield;
};
vector<VertexMap>& _vmaps;
size_t _max_n;
template <class Graph1, class Graph2, class VertexMap>
GetMatch<Graph1, Graph2, VertexMap> get_match(const Graph1& sub,
const Graph2& g,
vector<VertexMap>&,
size_t)
{
return GetMatch<Graph1, Graph2, VertexMap>(sub, g, _yield);
}
coro_t::push_type& _yield;
};
#endif // HAVE_BOOST_COROUTINE
struct get_subgraphs
{
template <class Graph1, class Graph2, class VertexLabel,
class EdgeLabel, class VertexMap>
class EdgeLabel, class VertexMap, class Matcher>
void operator()(const Graph1& sub, const Graph2* g,
VertexLabel vertex_label1, boost::any avertex_label2,
EdgeLabel edge_label1, boost::any aedge_label2,
vector<VertexMap>& vmaps, size_t max_n, bool induced,
bool iso) const
bool iso, Matcher m) const
{
VertexLabel vertex_label2 = any_cast<VertexLabel>(avertex_label2);
EdgeLabel edge_label2 = any_cast<EdgeLabel>(aedge_label2);
get_match<Graph1, Graph2, VertexMap> matcher(sub, *g, vmaps, max_n);
auto matcher = m.get_match(sub, *g, vmaps,max_n);
typedef typename graph_traits<Graph1>::vertex_descriptor vertex_t;
vector<vertex_t> vorder;
......@@ -110,11 +177,11 @@ struct get_subgraphs
};
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, size_t max_n, bool induced,
bool iso)
boost::python::object
subgraph_isomorphism(GraphInterface& gi1, GraphInterface& gi2,
boost::any vertex_label1, boost::any vertex_label2,
boost::any edge_label1, boost::any edge_label2,
size_t max_n, bool induced, bool iso, bool generator)
{
// typedef mpl::push_back<vertex_properties,
// ConstantPropertyMap<bool,GraphInterface::vertex_t> >
......@@ -138,7 +205,7 @@ void subgraph_isomorphism(GraphInterface& gi1, GraphInterface& gi2,
if (gi1.get_directed() != gi2.get_directed())
return;
return boost::python::object();
if (vertex_label1.empty() || vertex_label2.empty())
{
......@@ -162,20 +229,44 @@ void subgraph_isomorphism(GraphInterface& gi1, GraphInterface& gi2,
edge_label2 = any_cast<elabel_t>(edge_label2).get_unchecked(gi2.get_edge_index_range());
}
vector<vlabel_t> vmaps;
typedef mpl::transform<graph_tool::detail::all_graph_views,
mpl::quote1<std::add_pointer> >::type graph_view_pointers;
run_action<>()
(gi1, std::bind(get_subgraphs(), placeholders::_1, placeholders::_2,
placeholders::_3, vertex_label2, placeholders::_4,
edge_label2, std::ref(vmaps), max_n, induced, iso),
graph_view_pointers(), vertex_props_t(),
edge_props_t())
(gi2.get_graph_view(), vertex_label1, edge_label1);
vector<vlabel_t> vmaps;
if (!generator)
{
run_action<>()
(gi1, std::bind(get_subgraphs(), placeholders::_1, placeholders::_2,
placeholders::_3, vertex_label2, placeholders::_4,
edge_label2, std::ref(vmaps), max_n, induced, iso,
ListMatch()),
graph_view_pointers(), vertex_props_t(),
edge_props_t())
(gi2.get_graph_view(), vertex_label1, edge_label1);
for (auto& vmap: vmaps)
vmapping.append(PythonPropertyMap<vlabel_t>(vmap));
python::list vmapping;
for (auto& vmap: vmaps)
vmapping.append(PythonPropertyMap<vlabel_t>(vmap));
return vmapping;
}
else
{
#ifdef HAVE_BOOST_COROUTINE
auto dispatch = [&](auto& yield)
{
run_action<>()
(gi1, std::bind(get_subgraphs(), placeholders::_1, placeholders::_2,
placeholders::_3, vertex_label2, placeholders::_4,
edge_label2, std::ref(vmaps), max_n, induced, iso,
GenMatch(yield)),
graph_view_pointers(), vertex_props_t(),
edge_props_t())(gi2.get_graph_view(),
vertex_label1, edge_label1);
};
CoroGenerator gen(dispatch);
return boost::python::object(gen);
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
}
}
......@@ -35,11 +35,12 @@ 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 maximal_planar(GraphInterface& gi);
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, size_t max_n, bool induced,
bool iso);
python::object subgraph_isomorphism(GraphInterface& gi1, GraphInterface& gi2,
boost::any vertex_label1,
boost::any vertex_label2,
boost::any edge_label1,
boost::any edge_label2, size_t max_n,
bool induced, bool iso, bool generator);
double reciprocity(GraphInterface& gi);
size_t sequential_coloring(GraphInterface& gi, boost::any order,
boost::any color);
......
......@@ -242,7 +242,7 @@ def isomorphism(g1, g2, vertex_inv1=None, vertex_inv2=None, isomap=False):
def subgraph_isomorphism(sub, g, max_n=0, vertex_label=None, edge_label=None,
induced=False, subgraph=True):
induced=False, subgraph=True, generator=False):
r"""Obtain all subgraph isomorphisms of `sub` in `g` (or at most `max_n` subgraphs, if `max_n > 0`).
......@@ -252,29 +252,35 @@ def subgraph_isomorphism(sub, g, max_n=0, vertex_label=None, edge_label=None,
Subgraph for which to be searched.
g : :class:`~graph_tool.Graph`
Graph in which the search is performed.
max_n : int (optional, default: `0`)
max_n : int (optional, default: ``0``)
Maximum number of matches to find. If `max_n == 0`, all matches are
found.
vertex_label : pair of :class:`~graph_tool.PropertyMap` (optional, default: `None`)
vertex_label : pair of :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
If provided, this should be a pair of :class:`~graph_tool.PropertyMap`
objects, belonging to `sub` and `g` (in this order), which specify vertex labels