From 45565cd162be8a7f3a8385dcc327877b4ff4e0bb Mon Sep 17 00:00:00 2001 From: Tiago de Paula Peixoto Date: Tue, 20 Oct 2015 15:33:45 +0200 Subject: [PATCH] Implement dfs/bfs/dijkstra/astar_iterator() --- configure.ac | 9 +- m4/ax_boost_coroutine.m4 | 122 ++++++++ src/graph/Makefile.am | 2 +- src/graph/centrality/Makefile.am | 2 +- src/graph/community/Makefile.am | 2 +- src/graph/graph_python_interface.cc | 16 +- src/graph/graph_python_interface.hh | 2 +- src/graph/search/Makefile.am | 2 +- src/graph/search/graph_astar.cc | 129 +++++++- src/graph/search/graph_astar_implicit.cc | 2 +- src/graph/search/graph_bellman_ford.cc | 2 +- src/graph/search/graph_bfs.cc | 63 +++- src/graph/search/graph_dfs.cc | 65 +++- src/graph/search/graph_dijkstra.cc | 125 +++++++- src/graph/topology/Makefile.am | 2 +- src/graph_tool/search/__init__.py | 383 +++++++++++++++++++++-- 16 files changed, 864 insertions(+), 64 deletions(-) create mode 100644 m4/ax_boost_coroutine.m4 diff --git a/configure.ac b/configure.ac index aabf08f1..6cce64f3 100644 --- a/configure.ac +++ b/configure.ac @@ -202,7 +202,7 @@ echo "===========================" dnl boost -AX_BOOST_BASE([[1.53.0]]) +AX_BOOST_BASE([[1.55.0]]) AX_BOOST_PYTHON if test "$BOOST_PYTHON_LIB" = ""; then AC_MSG_ERROR([No usable boost::python found]) @@ -215,7 +215,10 @@ AX_BOOST_REGEX if test "$BOOST_REGEX_LIB" = ""; then AC_MSG_ERROR([No usable boost::regex found]) fi - +AX_BOOST_COROUTINE +if test "$BOOST_COROUTINE_LIB" = ""; then + AC_MSG_ERROR([No usable boost::coroutine found]) +fi AX_BOOST_GRAPH if test "$BOOST_GRAPH_LIB" = ""; then AC_MSG_ERROR([No usable boost::graph found]) @@ -407,7 +410,7 @@ AC_SUBST(MOD_CPPFLAGS) # default LIBADD flags for submodules [MOD_LIBADD="${PYTHON_LDFLAGS} -l${BOOST_IOSTREAMS_LIB} -l${BOOST_PYTHON_LIB} \ --l${BOOST_REGEX_LIB} ${OPENMP_LDFLAGS} -lexpat"] +-l${BOOST_REGEX_LIB} ${BOOST_COROUTINE_LIB} ${OPENMP_LDFLAGS} -lexpat"] AC_SUBST(MOD_LIBADD) # needed for typeinfo objects to work across DSO boundaries. diff --git a/m4/ax_boost_coroutine.m4 b/m4/ax_boost_coroutine.m4 new file mode 100644 index 00000000..4466d13e --- /dev/null +++ b/m4/ax_boost_coroutine.m4 @@ -0,0 +1,122 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_coroutine.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_COROUTINE +# +# DESCRIPTION +# +# Test for Coroutine library from the Boost C++ libraries. The macro +# requires a preceding call to AX_BOOST_BASE. Further documentation is +# available at . +# +# This macro calls: +# +# AC_SUBST(BOOST_COROUTINE_LIB) +# +# And sets: +# +# HAVE_BOOST_COROUTINE +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2013 Daniel Casimiro +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 1 + +AC_DEFUN([AX_BOOST_COROUTINE], +[ + AC_ARG_WITH([boost-coroutine], + AS_HELP_STRING([--with-boost-coroutine@<:@=special-lib@:>@], + [use the Coroutine library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-coroutine=boost_coroutine-gcc-mt ]), [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_coroutine_lib="" + else + want_boost="yes" + ax_boost_user_coroutine_lib="$withval" + fi + ], [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::Coroutine library is available, + ax_cv_boost_coroutine, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[@%:@include ]], + [[boost::coroutines::coroutine< void() > f;]])], + ax_cv_boost_coroutine=yes, ax_cv_boost_coroutine=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + + if test "x$ax_cv_boost_coroutine" = "xyes"; then + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_COROUTINE,,[define if the Boost::Coroutine library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + if test "x$ax_boost_user_coroutine_lib" = "x"; then + for libextension in `ls $BOOSTLIBDIR/libboost_coroutine*.so* $BOOSTLIBDIR/libboost_coroutine*.dylib* $BOOSTLIBDIR/libboost_coroutine*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_coroutine.*\)\.so.*$;\1;' -e 's;^lib\(boost_coroutine.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_coroutine.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_COROUTINE_LIB="-l$ax_lib"; AC_SUBST(BOOST_COROUTINE_LIB) link_coroutine="yes"; break], + [link_coroutine="no"]) + done + + if test "x$link_coroutine" != "xyes"; then + for libextension in `ls $BOOSTLIBDIR/boost_coroutine*.dll* $BOOSTLIBDIR/boost_coroutine*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_coroutine.*\)\.dll.*$;\1;' -e 's;^\(boost_coroutine.*\)\.a.*$;\1;'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_COROUTINE_LIB="-l$ax_lib"; AC_SUBST(BOOST_COROUTINE_LIB) link_coroutine="yes"; break], + [link_coroutine="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_coroutine_lib boost_coroutine-$ax_boost_user_coroutine_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_COROUTINE_LIB="-l$ax_lib"; AC_SUBST(BOOST_COROUTINE_LIB) link_coroutine="yes"; break], + [link_coroutine="no"]) + done + fi + + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the library!) + fi + + if test "x$link_coroutine" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/src/graph/Makefile.am b/src/graph/Makefile.am index 1248edc0..314c4389 100644 --- a/src/graph/Makefile.am +++ b/src/graph/Makefile.am @@ -12,7 +12,7 @@ AM_CXXFLAGS =\ $(PYTHON_CPPFLAGS) \ $(BOOST_CPPFLAGS) -AM_CFLAGS=$(AM_CXXFLAGS) +AM_CFLAGS = $(AM_CXXFLAGS) libgraph_tool_coredir = $(MOD_DIR) libgraph_tool_core_LTLIBRARIES = libgraph_tool_core.la diff --git a/src/graph/centrality/Makefile.am b/src/graph/centrality/Makefile.am index 8e5aac7b..a319f01d 100644 --- a/src/graph/centrality/Makefile.am +++ b/src/graph/centrality/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = $(MOD_CPPFLAGS) -AM_CFLAGS=$(AM_CXXFLAGS) +AM_CFLAGS = $(AM_CXXFLAGS) libgraph_tool_centralitydir = $(MOD_DIR)/centrality diff --git a/src/graph/community/Makefile.am b/src/graph/community/Makefile.am index 0d1f5257..543f26d7 100644 --- a/src/graph/community/Makefile.am +++ b/src/graph/community/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = $(MOD_CPPFLAGS) -AM_CFLAGS=$(AM_CXXFLAGS) +AM_CFLAGS = $(AM_CXXFLAGS) libgraph_tool_communitydir = $(MOD_DIR)/community diff --git a/src/graph/graph_python_interface.cc b/src/graph/graph_python_interface.cc index e26c112d..8f1fa855 100644 --- a/src/graph/graph_python_interface.cc +++ b/src/graph/graph_python_interface.cc @@ -412,18 +412,18 @@ struct export_python_interface ("VertexIterator", no_init) .def("__iter__", objects::identity_function()) .def("__next__", &PythonIterator, - vertex_iterator>::Next) + vertex_iterator>::next) .def("next", &PythonIterator, - vertex_iterator>::Next); + vertex_iterator>::next); typedef typename graph_traits::edge_iterator edge_iterator; class_, edge_iterator> >("EdgeIterator", no_init) .def("__iter__", objects::identity_function()) .def("__next__", &PythonIterator, - edge_iterator>::Next) + edge_iterator>::next) .def("next", &PythonIterator, - edge_iterator>::Next); + edge_iterator>::next); typedef typename graph_traits::out_edge_iterator out_edge_iterator; @@ -431,9 +431,9 @@ struct export_python_interface out_edge_iterator> >("OutEdgeIterator", no_init) .def("__iter__", objects::identity_function()) .def("__next__", &PythonIterator, - out_edge_iterator>::Next) + out_edge_iterator>::next) .def("next", &PythonIterator, - out_edge_iterator>::Next); + out_edge_iterator>::next); typedef typename graph_traits::directed_category directed_category; @@ -446,9 +446,9 @@ struct export_python_interface in_edge_iterator> >("InEdgeIterator", no_init) .def("__iter__", objects::identity_function()) .def("__next__", &PythonIterator, - in_edge_iterator>::Next) + in_edge_iterator>::next) .def("next", &PythonIterator, - in_edge_iterator>::Next); + in_edge_iterator>::next); } } diff --git a/src/graph/graph_python_interface.hh b/src/graph/graph_python_interface.hh index 903ba9cb..511571f0 100644 --- a/src/graph/graph_python_interface.hh +++ b/src/graph/graph_python_interface.hh @@ -65,7 +65,7 @@ public: PythonIterator(std::shared_ptr& gp, std::pair e) : _g(gp), _e(e) {} - Descriptor Next() + Descriptor next() { if (_e.first == _e.second) boost::python::objects::stop_iteration_error(); diff --git a/src/graph/search/Makefile.am b/src/graph/search/Makefile.am index 221b2dd8..43121494 100644 --- a/src/graph/search/Makefile.am +++ b/src/graph/search/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = $(MOD_CPPFLAGS) -AM_CFLAGS=$(AM_CXXFLAGS) +AM_CFLAGS = $(AM_CXXFLAGS) libgraph_tool_searchdir = $(MOD_DIR)/search diff --git a/src/graph/search/graph_astar.cc b/src/graph/search/graph_astar.cc index 853391b6..b4e98521 100644 --- a/src/graph/search/graph_astar.cc +++ b/src/graph/search/graph_astar.cc @@ -20,6 +20,7 @@ #include #include +#include #include "graph.hh" #include "graph_selectors.hh" @@ -39,19 +40,16 @@ python::object operator |(const python::object& a, const T& b) struct do_astar_search { - template - void operator()(Graph& g, size_t s, DistanceMap dist, boost::any pred_map, - boost::any aweight, AStarVisitorWrapper vis, pair cmp, pair range, + template + void operator()(Graph& g, size_t s, DistanceMap dist, PredMap pred, + boost::any aweight, Visitor vis, pair cmp, + pair range, python::object h, GraphInterface& gi) const { typedef typename graph_traits::edge_descriptor edge_t; typedef typename property_traits::value_type dtype_t; dtype_t z = python::extract(range.first); dtype_t i = python::extract(range.second); - typedef typename property_map_type:: - apply::type pred_t; - pred_t pred = any_cast(pred_map); checked_vector_property_map color(get(vertex_index, g)); @@ -66,23 +64,138 @@ struct do_astar_search } }; +struct do_astar_search_fast +{ + template + void operator()(Graph& g, size_t s, DistanceMap dist, + WeightMap weight, Visitor vis, + pair range, + python::object h, GraphInterface& gi) const + { + typedef typename property_traits::value_type dtype_t; + dtype_t z = python::extract(range.first); + dtype_t i = python::extract(range.second); + astar_search(g, vertex(s, g), AStarH(gi, g, h), + weight_map(weight).distance_map(dist).distance_zero(z). + distance_inf(i).visitor(vis)); + } +}; + void a_star_search(GraphInterface& g, size_t source, boost::any dist_map, boost::any pred_map, boost::any weight, python::object vis, python::object cmp, python::object cmb, python::object zero, python::object inf, python::object h) { + typedef typename property_map_type:: + apply::type pred_t; + pred_t pred = any_cast(pred_map); run_action() (g, std::bind(do_astar_search(), placeholders::_1, source, - placeholders::_2, pred_map, weight, + placeholders::_2, pred, weight, AStarVisitorWrapper(g, vis), make_pair(AStarCmp(cmp), AStarCmb(cmb)), make_pair(zero, inf), h, std::ref(g)), writable_vertex_properties())(dist_map); } + +typedef boost::coroutines::asymmetric_coroutine coro_t; + +class AStarGeneratorVisitor : public astar_visitor<> +{ +public: + AStarGeneratorVisitor(GraphInterface& gi, + coro_t::push_type& yield) + : _gi(gi), _yield(yield) {} + + template + void edge_relaxed(const Edge& e, Graph& g) + { + std::shared_ptr gp = retrieve_graph_view(_gi, g); + _yield(boost::python::object(PythonEdge(gp, e))); + } + +private: + GraphInterface& _gi; + coro_t::push_type& _yield; +}; + +class AStarGenerator +{ +public: + template + AStarGenerator(Dispatch& dispatch) + : _coro(std::make_shared(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; + coro_t::pull_type::iterator _iter; + coro_t::pull_type::iterator _end; +}; + +boost::python::object astar_search_generator(GraphInterface& g, + size_t source, + boost::any dist_map, + boost::any weight, + python::object cmp, + python::object cmb, + python::object zero, + python::object inf, + python::object h) +{ + auto dispatch = [&](auto& yield) + { + AStarGeneratorVisitor vis(g, yield); + run_action() + (g, std::bind(do_astar_search(), placeholders::_1, source, + placeholders::_2, dummy_property_map(), weight, + vis, make_pair(AStarCmp(cmp), AStarCmb(cmb)), + make_pair(zero, inf), h, std::ref(g)), + writable_vertex_properties())(dist_map); + }; + return boost::python::object(AStarGenerator(dispatch)); +} + +boost::python::object astar_search_generator_fast(GraphInterface& g, + size_t source, + boost::any dist_map, + boost::any weight, + python::object zero, + python::object inf, + python::object h) +{ + auto dispatch = [&](auto& yield) + { + AStarGeneratorVisitor vis(g, yield); + run_action() + (g, std::bind(do_astar_search_fast(), placeholders::_1, source, + placeholders::_2, placeholders::_3, + vis, make_pair(zero, inf), h, std::ref(g)), + writable_vertex_scalar_properties(), + edge_scalar_properties())(dist_map, weight); + }; + return boost::python::object(AStarGenerator(dispatch)); +} + + void export_astar() { using namespace boost::python; def("astar_search", &a_star_search); + def("astar_generator", &astar_search_generator); + def("astar_generator_fast", &astar_search_generator_fast); + class_("AStarGenerator", no_init) + .def("__iter__", objects::identity_function()) + .def("next", &AStarGenerator::next) + .def("__next__", &AStarGenerator::next); } diff --git a/src/graph/search/graph_astar_implicit.cc b/src/graph/search/graph_astar_implicit.cc index 7ef6f669..27b444b2 100644 --- a/src/graph/search/graph_astar_implicit.cc +++ b/src/graph/search/graph_astar_implicit.cc @@ -50,7 +50,7 @@ struct do_astar_search decltype(get(vertex_index, g))> color(get(vertex_index, g)); typedef typename property_map_type:: - apply::type pred_t; + apply::type pred_t; typedef typename graph_traits::edge_descriptor edge_t; DynamicPropertyMapWrap weight(aweight, edge_properties()); diff --git a/src/graph/search/graph_bellman_ford.cc b/src/graph/search/graph_bellman_ford.cc index b10200b3..a611ca20 100644 --- a/src/graph/search/graph_bellman_ford.cc +++ b/src/graph/search/graph_bellman_ford.cc @@ -127,7 +127,7 @@ struct do_bf_search dtype_t i = python::extract(range.second); typedef typename property_map_type:: - apply::type pred_t; + apply::type pred_t; pred_t pred = any_cast(pred_map); typedef typename graph_traits::edge_descriptor edge_t; DynamicPropertyMapWrap weight(aweight, diff --git a/src/graph/search/graph_bfs.cc b/src/graph/search/graph_bfs.cc index 21cba4ef..63d76e35 100644 --- a/src/graph/search/graph_bfs.cc +++ b/src/graph/search/graph_bfs.cc @@ -20,6 +20,7 @@ #include #include +#include #include "graph.hh" #include "graph_selectors.hh" @@ -105,8 +106,8 @@ private: struct do_bfs { - template - void operator()(Graph& g, size_t s, BFSVisitorWrapper vis) const + template + void operator()(Graph& g, size_t s, Visitor vis) const { breadth_first_search(g, vertex(s, g), visitor(vis)); } @@ -119,8 +120,66 @@ void bfs_search(GraphInterface& g, size_t s, python::object vis) BFSVisitorWrapper(g, vis)))(); } +typedef boost::coroutines::asymmetric_coroutine coro_t; + +class BFSGeneratorVisitor : public bfs_visitor<> +{ +public: + BFSGeneratorVisitor(GraphInterface& gi, + coro_t::push_type& yield) + : _gi(gi), _yield(yield) {} + + template + void tree_edge(const Edge& e, Graph& g) + { + std::shared_ptr gp = retrieve_graph_view(_gi, g); + _yield(boost::python::object(PythonEdge(gp, e))); + } + +private: + GraphInterface& _gi; + coro_t::push_type& _yield; +}; + +class BFSGenerator +{ +public: + template + BFSGenerator(Dispatch& dispatch) + : _coro(std::make_shared(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; + coro_t::pull_type::iterator _iter; + coro_t::pull_type::iterator _end; +}; + +boost::python::object bfs_search_generator(GraphInterface& g, size_t s) +{ + auto dispatch = [&](auto& yield) + { + BFSGeneratorVisitor vis(g, yield); + run_action() + (g, std::bind(do_bfs(), placeholders::_1, s, vis))(); + }; + return boost::python::object(BFSGenerator(dispatch)); +} + void export_bfs() { using namespace boost::python; def("bfs_search", &bfs_search); + def("bfs_search_generator", &bfs_search_generator); + class_("BFSGenerator", no_init) + .def("__iter__", objects::identity_function()) + .def("next", &BFSGenerator::next) + .def("__next__", &BFSGenerator::next); } diff --git a/src/graph/search/graph_dfs.cc b/src/graph/search/graph_dfs.cc index 22638384..9a397500 100644 --- a/src/graph/search/graph_dfs.cc +++ b/src/graph/search/graph_dfs.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include "graph.hh" #include "graph_selectors.hh" @@ -89,9 +90,9 @@ private: struct do_dfs { - template + template void operator()(Graph& g, VertexIndexMap vertex_index, size_t s, - DFSVisitorWrapper vis) const + Visitor vis) const { typename property_map_type::apply::type @@ -108,8 +109,68 @@ void dfs_search(GraphInterface& g, size_t s, python::object vis) s, DFSVisitorWrapper(g, vis)))(); } + +typedef boost::coroutines::asymmetric_coroutine coro_t; + +class DFSGeneratorVisitor : public dfs_visitor<> +{ +public: + DFSGeneratorVisitor(GraphInterface& gi, + coro_t::push_type& yield) + : _gi(gi), _yield(yield) {} + + template + void tree_edge(const Edge& e, Graph& g) + { + std::shared_ptr gp = retrieve_graph_view(_gi, g); + _yield(boost::python::object(PythonEdge(gp, e))); + } + +private: + GraphInterface& _gi; + coro_t::push_type& _yield; +}; + +class DFSGenerator +{ +public: + template + DFSGenerator(Dispatch& dispatch) + : _coro(std::make_shared(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; + coro_t::pull_type::iterator _iter; + coro_t::pull_type::iterator _end; +}; + +boost::python::object dfs_search_generator(GraphInterface& g, size_t s) +{ + auto dispatch = [&](auto& yield) + { + DFSGeneratorVisitor vis(g, yield); + run_action() + (g, std::bind(do_dfs(), placeholders::_1, + g.get_vertex_index(), s, vis))(); + }; + return boost::python::object(DFSGenerator(dispatch)); +} + void export_dfs() { using namespace boost::python; def("dfs_search", &dfs_search); + def("dfs_search_generator", &dfs_search_generator); + class_("DFSGenerator", no_init) + .def("__iter__", objects::identity_function()) + .def("next", &DFSGenerator::next) + .def("__next__", &DFSGenerator::next); } diff --git a/src/graph/search/graph_dijkstra.cc b/src/graph/search/graph_dijkstra.cc index aaa6aa42..1425a62e 100644 --- a/src/graph/search/graph_dijkstra.cc +++ b/src/graph/search/graph_dijkstra.cc @@ -20,6 +20,7 @@ #include #include +#include #include "graph.hh" #include "graph_selectors.hh" @@ -125,45 +126,151 @@ private: struct do_djk_search { - template + template void operator()(const Graph& g, size_t s, DistanceMap dist, - boost::any pred_map, boost::any aweight, - DJKVisitorWrapper vis, const DJKCmp& cmp, const DJKCmb& cmb, + PredMap pred_map, boost::any aweight, + Visitor vis, const DJKCmp& cmp, const DJKCmb& cmb, pair range) const { typedef typename property_traits::value_type dtype_t; dtype_t z = python::extract(range.first); dtype_t i = python::extract(range.second); - typedef typename property_map_type:: - apply::type pred_t; - pred_t pred = any_cast(pred_map); typedef typename graph_traits::edge_descriptor edge_t; DynamicPropertyMapWrap weight(aweight, edge_properties()); dijkstra_shortest_paths_no_color_map (g, vertex(s, g), visitor(vis).weight_map(weight). - predecessor_map(pred). + predecessor_map(pred_map). distance_map(dist).distance_compare(cmp). distance_combine(cmb).distance_inf(i).distance_zero(z)); } }; +struct do_djk_search_fast +{ + template + void operator()(const Graph& g, size_t s, DistanceMap dist, + WeightMap weight, Visitor vis, + pair range) const + { + typedef typename property_traits::value_type dtype_t; + dtype_t z = python::extract(range.first); + dtype_t i = python::extract(range.second); + dijkstra_shortest_paths_no_color_map + (g, vertex(s, g), visitor(vis).weight_map(weight). + distance_map(dist).distance_inf(i).distance_zero(z)); + } +}; + void dijkstra_search(GraphInterface& g, size_t source, boost::any dist_map, boost::any pred_map, boost::any weight, python::object vis, python::object cmp, python::object cmb, python::object zero, python::object inf) { - run_action() + typedef typename property_map_type:: + apply::type pred_t; + pred_t pred = any_cast(pred_map); + run_action() (g, std::bind(do_djk_search(), placeholders::_1, source, - placeholders::_2, pred_map, weight, + placeholders::_2, pred, weight, DJKVisitorWrapper(g, vis), DJKCmp(cmp), DJKCmb(cmb), make_pair(zero, inf)), writable_vertex_properties())(dist_map); } + +typedef boost::coroutines::asymmetric_coroutine coro_t; + +class DJKGeneratorVisitor : public dijkstra_visitor<> +{ +public: + DJKGeneratorVisitor(GraphInterface& gi, + coro_t::push_type& yield) + : _gi(gi), _yield(yield) {} + + template + void edge_relaxed(const Edge& e, Graph& g) + { + std::shared_ptr gp = retrieve_graph_view(_gi, g); + _yield(boost::python::object(PythonEdge(gp, e))); + } + +private: + GraphInterface& _gi; + coro_t::push_type& _yield; +}; + +class DJKGenerator +{ +public: + template + DJKGenerator(Dispatch& dispatch) + : _coro(std::make_shared(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; + coro_t::pull_type::iterator _iter; + coro_t::pull_type::iterator _end; +}; + +boost::python::object dijkstra_search_generator(GraphInterface& g, + size_t source, + boost::any dist_map, + boost::any weight, + python::object cmp, + python::object cmb, + python::object zero, + python::object inf) +{ + auto dispatch = [&](auto& yield) + { + DJKGeneratorVisitor vis(g, yield); + run_action() + (g, std::bind(do_djk_search(), placeholders::_1, source, + placeholders::_2, dummy_property_map(), weight, + vis, DJKCmp(cmp), DJKCmb(cmb), + make_pair(zero, inf)), + writable_vertex_properties())(dist_map); + }; + return boost::python::object(DJKGenerator(dispatch)); +} + +boost::python::object dijkstra_search_generator_fast(GraphInterface& g, + size_t source, + boost::any dist_map, + boost::any weight, + python::object zero, python::object inf) +{ + auto dispatch = [&](auto& yield) + { + DJKGeneratorVisitor vis(g, yield); + run_action() + (g, std::bind(do_djk_search_fast(), placeholders::_1, source, + placeholders::_2, placeholders::_3, + vis, make_pair(zero, inf)), + writable_vertex_scalar_properties(), + edge_scalar_properties())(dist_map, weight); + }; + return boost::python::object(DJKGenerator(dispatch)); +} + void export_dijkstra() { using namespace boost::python; def("dijkstra_search", &dijkstra_search); + def("dijkstra_generator", &dijkstra_search_generator); + def("dijkstra_generator_fast", &dijkstra_search_generator_fast); + class_("DJKGenerator", no_init) + .def("__iter__", objects::identity_function()) + .def("next", &DJKGenerator::next) + .def("__next__", &DJKGenerator::next); } diff --git a/src/graph/topology/Makefile.am b/src/graph/topology/Makefile.am index 66d4b614..45f1b813 100644 --- a/src/graph/topology/Makefile.am +++ b/src/graph/topology/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = $(MOD_CPPFLAGS) -AM_CFLAGS=$(AM_CXXFLAGS) +AM_CFLAGS = $(AM_CXXFLAGS) libgraph_tool_topologydir = $(MOD_DIR)/topology diff --git a/src/graph_tool/search/__init__.py b/src/graph_tool/search/__init__.py index e68c5dd2..6d7f6e16 100644 --- a/src/graph_tool/search/__init__.py +++ b/src/graph_tool/search/__init__.py @@ -35,10 +35,14 @@ Summary :nosignatures: bfs_search + bfs_iterator dfs_search + dfs_iterator dijkstra_search - bellman_ford_search + dijkstra_iterator astar_search + astar_iterator + bellman_ford_search BFSVisitor DFSVisitor DijkstraVisitor @@ -90,11 +94,12 @@ dl_import("from . import libgraph_tool_search") from .. import _prop, _python_type import weakref +import numpy -__all__ = ["bfs_search", "BFSVisitor", "dfs_search", "DFSVisitor", - "dijkstra_search", "DijkstraVisitor", "bellman_ford_search", - "BellmanFordVisitor", "astar_search", "AStarVisitor", - "StopSearch"] +__all__ = ["bfs_search", "bfs_iterator", "BFSVisitor", "dfs_search", + "dfs_iterator", "DFSVisitor", "dijkstra_search", "dijkstra_iterator", + "DijkstraVisitor", "bellman_ford_search", "BellmanFordVisitor", + "astar_search", "astar_iterator", "AStarVisitor", "StopSearch"] class BFSVisitor(object): @@ -242,7 +247,7 @@ def bfs_search(g, source, visitor=BFSVisitor()): With the above class defined, we can perform the BFS search as follows. >>> dist = g.new_vertex_property("int") - >>> pred = g.new_vertex_property("int") + >>> pred = g.new_vertex_property("int64_t") >>> gt.bfs_search(g, g.vertex(0), VisitorExample(name, pred, dist)) --> Bob has been discovered! Bob has been examined... @@ -284,6 +289,61 @@ def bfs_search(g, source, visitor=BFSVisitor()): except StopSearch: pass +def bfs_iterator(g, source): + r"""Return an iterator of the edges corresponding to a breath-first traversal of + the graph. + + Parameters + ---------- + g : :class:`~graph_tool.Graph` + Graph to be used. + source : :class:`~graph_tool.Vertex` + Source vertex. + + Returns + ------- + bfs_iterator : An iterator over the edges in breath-first order. + + See Also + -------- + dfs_iterator: Depth-first search + dijkstra_iterator: Dijkstra's search algorithm + astar_iterator: :math:`A^*` heuristic search algorithm + + Notes + ----- + + See :func:`~graph_tool.search.bfs_search` for an explanation of the + algorithm. + + The time complexity is :math:`O(1)` to create the generator and + :math:`O(V + E)` to traverse it completely. + + Examples + -------- + + >>> for e in gt.bfs_iterator(g, g.vertex(0)): + ... print(name[e.source()], "->", name[e.target()]) + Bob -> Eve + Bob -> Chuck + Bob -> Carlos + Bob -> Isaac + Eve -> Carol + Eve -> Imothep + Carlos -> Alice + Alice -> Oscar + Alice -> Dave + + References + ---------- + .. [bfs] Edward Moore, "The shortest path through a maze", International + Symposium on the Theory of Switching, 1959 + .. [bfs-bgl] http://www.boost.org/doc/libs/release/libs/graph/doc/breadth_first_search.html + .. [bfs-wikipedia] http://en.wikipedia.org/wiki/Breadth-first_search + """ + + return libgraph_tool_search.bfs_search_generator(g._Graph__graph, int(source)) + class DFSVisitor(object): r""" @@ -448,7 +508,7 @@ def dfs_search(g, source, visitor=DFSVisitor()): With the above class defined, we can perform the DFS search as follows. >>> time = g.new_vertex_property("int") - >>> pred = g.new_vertex_property("int") + >>> pred = g.new_vertex_property("int64_t") >>> gt.dfs_search(g, g.vertex(0), VisitorExample(name, pred, time)) --> Bob has been discovered! edge (Bob, Eve) has been examined... @@ -511,6 +571,58 @@ def dfs_search(g, source, visitor=DFSVisitor()): except StopSearch: pass +def dfs_iterator(g, source): + r"""Return an iterator of the edges corresponding to a depth-first traversal of + the graph. + + Parameters + ---------- + g : :class:`~graph_tool.Graph` + Graph to be used. + source : :class:`~graph_tool.Vertex` + Source vertex. + + Returns + ------- + dfs_iterator : An iterator over the edges in detpth-first order. + + See Also + -------- + bfs_iterator: Breadth-first search + dijkstra_iterator: Dijkstra's search algorithm + astar_iterator: :math:`A^*` heuristic search algorithm + + Notes + ----- + + See :func:`~graph_tool.search.dfs_search` for an explanation of the + algorithm. + + The time complexity is :math:`O(1)` to create the generator and + :math:`O(V + E)` to traverse it completely. + + Examples + -------- + + >>> for e in gt.dfs_iterator(g, g.vertex(0)): + ... print(name[e.source()], "->", name[e.target()]) + Bob -> Eve + Eve -> Carol + Carol -> Imothep + Imothep -> Carlos + Carlos -> Alice + Alice -> Oscar + Oscar -> Dave + Imothep -> Chuck + Chuck -> Isaac + + References + ---------- + .. [dfs-bgl] http://www.boost.org/doc/libs/release/libs/graph/doc/depth_first_search.html + .. [dfs-wikipedia] http://en.wikipedia.org/wiki/Depth-first_search + """ + + return libgraph_tool_search.dfs_search_generator(g._Graph__graph, int(source)) class DijkstraVisitor(object): r"""A visitor object that is invoked at the event-points inside the @@ -576,7 +688,7 @@ class DijkstraVisitor(object): def dijkstra_search(g, source, weight, visitor=DijkstraVisitor(), dist_map=None, pred_map=None, combine=lambda a, b: a + b, - compare=lambda a, b: a < b, zero=0, infinity=float('inf')): + compare=lambda a, b: a < b, zero=0, infinity=numpy.inf): r"""Dijsktra traversal of a directed or undirected graph, with non-negative weights. Parameters @@ -596,7 +708,7 @@ def dijkstra_search(g, source, weight, visitor=DijkstraVisitor(), dist_map=None, stored. pred_map : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) A vertex property map where the predecessor map will be - stored (must have value type "int"). + stored (must have value type "int64_t"). combine : binary function (optional, default: ``lambda a, b: a + b``) This function is used to combine distances to compute the distance of a path. @@ -606,7 +718,7 @@ def dijkstra_search(g, source, weight, visitor=DijkstraVisitor(), dist_map=None, zero : int or float (optional, default: ``0``) Value assumed to correspond to a distance of zero by the combine and compare functions. - infinity : int or float (optional, default: ``float('inf')``) + infinity : int or float (optional, default: ``numpy.inf``) Value assumed to correspond to a distance of infinity by the combine and compare functions. @@ -639,7 +751,7 @@ def dijkstra_search(g, source, weight, visitor=DijkstraVisitor(), dist_map=None, top of the priority queue. The algorithm finishes when the priority queue is empty. - The time complexity is :math:`O(V \log V)`. + The time complexity is :math:`O(E + V \log V)`. The pseudo-code for Dijkstra's algorithm is listed below, with the annotated event points, for which the given visitor object will be called with the @@ -778,9 +890,9 @@ def dijkstra_search(g, source, weight, visitor=DijkstraVisitor(), dist_map=None, if dist_map is None: dist_map = g.new_vertex_property(weight.value_type()) if pred_map is None: - pred_map = g.new_vertex_property("int") - if pred_map.value_type() != "int32_t": - raise ValueError("pred_map must be of value type 'int32_t', not '%s'." % \ + pred_map = g.new_vertex_property("int64_t") + if pred_map.value_type() != "int64_t": + raise ValueError("pred_map must be of value type 'int64_t', not '%s'." % \ pred_map.value_type()) try: @@ -809,6 +921,113 @@ def dijkstra_search(g, source, weight, visitor=DijkstraVisitor(), dist_map=None, return dist_map, pred_map +def dijkstra_iterator(g, source, weight, dist_map=None, combine=None, + compare=None, zero=0, infinity=numpy.inf): + r"""Return an iterator of the edges corresponding to a Dijkstra traversal of + the graph. + + Parameters + ---------- + g : :class:`~graph_tool.Graph` + Graph to be used. + source : :class:`~graph_tool.Vertex` + Source vertex. + weight : :class:`~graph_tool.PropertyMap` + Edge property map with weight values. + dist_map : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) + A vertex property map where the distances from the source will be + stored. + combine : binary function (optional, default: ``lambda a, b: a + b``) + This function is used to combine distances to compute the distance of a + path. + compare : binary function (optional, default: ``lambda a, b: a < b``) + This function is use to compare distances to determine which vertex is + closer to the source vertex. + zero : int or float (optional, default: ``0``) + Value assumed to correspond to a distance of zero by the combine and + compare functions. + infinity : int or float (optional, default: ``numpy.inf``) + Value assumed to correspond to a distance of infinity by the combine and + compare functions. + + Returns + ------- + djk_iterator : An iterator over the edges in Dijkstra order. + + See Also + -------- + bfs_iterator: Breadth-first search + dfs_iterator: Depth-first search + astar_iterator: :math:`A^*` heuristic search algorithm + + Notes + ----- + + See :func:`~graph_tool.search.dijkstra_search` for an explanation of the + algorithm. + + The time complexity is :math:`O(1)` to create the generator and + :math:`O(E + V\log V)` to traverse it completely. + + Examples + -------- + + >>> for e in gt.dijkstra_iterator(g, g.vertex(0), weight): + ... print(name[e.source()], "->", name[e.target()]) + Bob -> Eve + Bob -> Chuck + Bob -> Carlos + Bob -> Isaac + Eve -> Carol + Eve -> Imothep + Carlos -> Alice + Alice -> Oscar + Alice -> Dave + + + References + ---------- + .. [dijkstra] E. Dijkstra, "A note on two problems in connexion with + graphs", Numerische Mathematik, 1:269-271, 1959. + .. [dijkstra-bgl] http://www.boost.org/doc/libs/release/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html + .. [dijkstra-wikipedia] http://en.wikipedia.org/wiki/Dijkstra's_algorithm + """ + + if dist_map is None: + dist_map = g.new_vertex_property(weight.value_type()) + + try: + if dist_map.value_type() != "python::object": + zero = _python_type(dist_map.value_type())(zero) + except OverflowError: + zero = (weight.a.max() + 1) * g.num_vertices() + zero = _python_type(dist_map.value_type())(zero) + + try: + if dist_map.value_type() != "python::object": + infinity = _python_type(dist_map.value_type())(infinity) + except OverflowError: + infinity = (weight.a.max() + 1) * g.num_vertices() + infinity = _python_type(dist_map.value_type())(infinity) + + if compare is None and combine is None: + return libgraph_tool_search.dijkstra_generator_fast(g._Graph__graph, + int(source), + _prop("v", g, dist_map), + _prop("e", g, weight), + zero, infinity) + else: + if compare is None: + compare = lambda a, b: a < b + if combine is None: + combine = lambda a, b: a + b + return libgraph_tool_search.dijkstra_generator(g._Graph__graph, + int(source), + _prop("v", g, dist_map), + _prop("e", g, weight), + compare, combine, + zero, infinity) + class BellmanFordVisitor(object): r"""A visitor object that is invoked at the event-points inside the @@ -880,7 +1099,7 @@ def bellman_ford_search(g, source, weight, visitor=BellmanFordVisitor(), stored. pred_map : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) A vertex property map where the predecessor map will be - stored (must have value type "int"). + stored (must have value type "int64_t"). combine : binary function (optional, default: ``lambda a, b: a + b``) This function is used to combine distances to compute the distance of a path. @@ -1039,9 +1258,9 @@ def bellman_ford_search(g, source, weight, visitor=BellmanFordVisitor(), if dist_map is None: dist_map = g.new_vertex_property(weight.value_type()) if pred_map is None: - pred_map = g.new_vertex_property("int") - if pred_map.value_type() != "int32_t": - raise ValueError("pred_map must be of value type 'int32_t', not '%s'." % \ + pred_map = g.new_vertex_property("int64_t") + if pred_map.value_type() != "int64_t": + raise ValueError("pred_map must be of value type 'int64_t', not '%s'." % \ pred_map.value_type()) try: @@ -1173,7 +1392,7 @@ def astar_search(g, source, weight, visitor=AStarVisitor(), stored. pred_map : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) A vertex property map where the predecessor map will be - stored (must have value type "int"). + stored (must have value type "int64_t"). cost_map : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) A vertex property map where the vertex costs will be stored. It must have the same value type as ``dist_map``. This parameter is only used if @@ -1224,7 +1443,7 @@ def astar_search(g, source, weight, visitor=AStarVisitor(), efficiency of :math:`A^*` is highly dependent on the heuristic function with which it is used. - The time complexity is :math:`O((E + V) \log V)`. + The time complexity is :math:`O((E + V)\log V)`. The pseudo-code for the :math:`A^*` algorithm is listed below, with the annotated event points, for which the given visitor object will be called @@ -1484,7 +1703,6 @@ def astar_search(g, source, weight, visitor=AStarVisitor(), References ---------- - .. [astar] Hart, P. E.; Nilsson, N. J.; Raphael, B. "A Formal Basis for the Heuristic Determination of Minimum Cost Paths". IEEE Transactions on Systems Science and Cybernetics SSC4 4 (2): 100-107, 1968. @@ -1496,9 +1714,9 @@ def astar_search(g, source, weight, visitor=AStarVisitor(), if dist_map is None: dist_map = g.new_vertex_property(weight.value_type()) if pred_map is None: - pred_map = g.new_vertex_property("int") - if pred_map.value_type() != "int32_t": - raise ValueError("pred_map must be of value type 'int32_t', not '%s'." % \ + pred_map = g.new_vertex_property("int64_t") + if pred_map.value_type() != "int64_t": + raise ValueError("pred_map must be of value type 'int64_t', not '%s'." % \ pred_map.value_type()) dist_type = dist_map.python_value_type() @@ -1546,6 +1764,123 @@ def astar_search(g, source, weight, visitor=AStarVisitor(), return dist_map, pred_map +def astar_iterator(g, source, weight, heuristic=lambda v: 1, dist_map=None, + combine=None, compare=None, zero=0, infinity=numpy.inf): + r"""Return an iterator of the edges corresponding to an :math:`A^*` traversal of + the graph. + + Parameters + ---------- + g : :class:`~graph_tool.Graph` + Graph to be used. + source : :class:`~graph_tool.Vertex` + Source vertex. + weight : :class:`~graph_tool.PropertyMap` + Edge property map with weight values. + heuristic : unary function (optional, default: ``lambda v: 1``) + The heuristic function that guides the search. It should take a single + argument which is a :class:`~graph_tool.Vertex`, and output an estimated + distance from the supplied vertex to the target vertex. + dist_map : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) + A vertex property map where the distances from the source will be + stored. + combine : binary function (optional, default: ``lambda a, b: a + b``) + This function is used to combine distances to compute the distance of a + path. + compare : binary function (optional, default: ``lambda a, b: a < b``) + This function is use to compare distances to determine which vertex is + closer to the source vertex. + zero : int or float (optional, default: ``0``) + Value assumed to correspond to a distance of zero by the combine and + compare functions. + infinity : int or float (optional, default: ``numpy.inf``) + Value assumed to correspond to a distance of infinity by the combine and + compare functions. + + Returns + ------- + astar_iterator : An iterator over the edges in :math:`A^*` order. + + See Also + -------- + bfs_iterator: Breadth-first search + dfs_iterator: Depth-first search + dijkstra_iterator: Dijkstra search algorithm + + Notes + ----- + + See :func:`~graph_tool.search.astar_search` for an explanation of the + algorithm. + + The time complexity is :math:`O(1)` to create the generator and + :math:`O((E + V)\log V)` to traverse it completely. + + Examples + -------- + + >>> g = gt.load_graph("search_example.xml") + >>> name = g.vp["name"] + >>> weight = g.ep["weight"] + >>> for e in gt.astar_iterator(g, g.vertex(0), weight): + ... print(name[e.source()], "->", name[e.target()]) + Bob -> Eve + Bob -> Chuck + Bob -> Carlos + Bob -> Isaac + Eve -> Carol + Eve -> Imothep + Carlos -> Alice + Alice -> Oscar + Alice -> Dave + + References + ---------- + .. [astar] Hart, P. E.; Nilsson, N. J.; Raphael, B. "A Formal Basis for the + Heuristic Determination of Minimum Cost Paths". IEEE Transactions on + Systems Science and Cybernetics SSC4 4 (2): 100-107, 1968. + :doi:`10.1109/TSSC.1968.300136` + .. [astar-bgl] http://www.boost.org/doc/libs/release/libs/graph/doc/astar_search.html + .. [astar-wikipedia] http://en.wikipedia.org/wiki/A*_search_algorithm + """ + + if dist_map is None: + dist_map = g.new_vertex_property(weight.value_type()) + + try: + if dist_map.value_type() != "python::object": + zero = _python_type(dist_map.value_type())(zero) + except OverflowError: + zero = (weight.a.max() + 1) * g.num_vertices() + zero = _python_type(dist_map.value_type())(zero) + + try: + if dist_map.value_type() != "python::object": + infinity = _python_type(dist_map.value_type())(infinity) + except OverflowError: + infinity = (weight.a.max() + 1) * g.num_vertices() + infinity = _python_type(dist_map.value_type())(infinity) + + if compare is None and combine is None: + return libgraph_tool_search.astar_generator_fast(g._Graph__graph, + int(source), + _prop("v", g, dist_map), + _prop("e", g, weight), + zero, infinity, heuristic) + else: + if compare is None: + compare = lambda a, b: a < b + if combine is None: + combine = lambda a, b: a + b + return libgraph_tool_search.astar_generator(g._Graph__graph, + int(source), + _prop("v", g, dist_map), + _prop("e", g, weight), + compare, combine, + zero, infinity, heuristic) + + + class StopSearch(Exception): """If this exception is raised from inside any search visitor object, the search is aborted.""" pass -- GitLab