Commit 866bb994 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Add support for running arbitrary C++ code from python

It is now possible to run arbitrary "inline" C++ code from python, using
scipy.weave, as such:

        g = graph_tool.Graph()
        g.load("foo.xml")
        value = 2.0
        g.run_action('cout << num_vertices(g) << " " << value << endl;',
                     ["value"]);

The code gets compiled the first time, and is reused after that. Python
local and global variables can be passed to C++, as shown above, by
specifying a list of passed variables as the second argument.
parent cb15708b
...@@ -12,19 +12,18 @@ AM_CXXFLAGS =\ ...@@ -12,19 +12,18 @@ AM_CXXFLAGS =\
AM_CFLAGS=$(AM_CXXFLAGS) AM_CFLAGS=$(AM_CXXFLAGS)
libdir = ${pythondir} libgraph_tool_ladir = ${pythondir}
libgraph_tool_includedir = $(includedir)/graph_tool
install-data-hook:
chmod 755 $(includedir)/graph_tool
lib_LTLIBRARIES = libgraph_tool.la lib_LTLIBRARIES = libgraph_tool.la
libgraph_tool_la_SOURCES = \ libgraph_tool_la_SOURCES = \
graph_adaptor.hh\
graph.hh\ graph.hh\
graph.cc\ graph.cc\
graph_filtering.hh\
graph_python_interface.hh\
graph_python_interface.cc\ graph_python_interface.cc\
graph_selectors.hh\
graph_properties.hh\
graph_properties.cc\ graph_properties.cc\
graph_correlations.cc\ graph_correlations.cc\
graph_edge_correlations.cc\ graph_edge_correlations.cc\
...@@ -47,13 +46,22 @@ libgraph_tool_la_SOURCES = \ ...@@ -47,13 +46,22 @@ libgraph_tool_la_SOURCES = \
graph_io.cc\ graph_io.cc\
graph_bind.cc\ graph_bind.cc\
graphml.cpp\ graphml.cpp\
histogram.hh\
shared_map.hh\
read_graphviz_spirit.cpp\ read_graphviz_spirit.cpp\
../boost-workaround/boost/graph/filtered_graph.hpp\ ../boost-workaround/boost/graph/filtered_graph.hpp\
../boost-workaround/boost/graph/fruchterman_reingold.hpp\ ../boost-workaround/boost/graph/fruchterman_reingold.hpp\
../boost-workaround/boost/graph/graphml.hpp ../boost-workaround/boost/graph/graphml.hpp
libgraph_tool_include_HEADERS = \
graph_adaptor.hh\
graph.hh\
graph_filtering.hh\
graph_python_interface.hh\
graph_selectors.hh\
graph_properties.hh\
shared_map.hh\
histogram.hh\
../../config.h
libgraph_tool_la_LIBADD = \ libgraph_tool_la_LIBADD = \
$(PYTHON_LDFLAGS) \ $(PYTHON_LDFLAGS) \
$(BOOST_LDFLAGS) \ $(BOOST_LDFLAGS) \
...@@ -61,4 +69,3 @@ libgraph_tool_la_LIBADD = \ ...@@ -61,4 +69,3 @@ libgraph_tool_la_LIBADD = \
-lboost_python \ -lboost_python \
-lboost_iostreams \ -lboost_iostreams \
-lexpat -lexpat
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <boost/python/object.hpp> #include <boost/python/object.hpp>
#include <boost/python/dict.hpp> #include <boost/python/dict.hpp>
#include <boost/lambda/lambda.hpp>
#include "histogram.hh" #include "histogram.hh"
#include "config.h" #include "config.h"
#include "graph_properties.hh" #include "graph_properties.hh"
...@@ -202,6 +203,15 @@ public: ...@@ -202,6 +203,15 @@ public:
// signal handling // signal handling
void InitSignalHandling(); void InitSignalHandling();
// arbitrary code execution, for run-time code integration
template <class Action, class Args>
void RunAction(const Action &a, const Args& args)
{
using namespace boost::lambda;
run_action(*this, bind<void>(a, boost::lambda::_1, _vertex_index,
_edge_index, var(_properties), var(args)));
}
// //
// Internal types // Internal types
// //
...@@ -232,12 +242,12 @@ private: ...@@ -232,12 +242,12 @@ private:
template <class GraphInterfaceType, class Action, class ReverseCheck, template <class GraphInterfaceType, class Action, class ReverseCheck,
class DirectedCheck> class DirectedCheck>
friend void run_action(GraphInterfaceType &g, Action a, friend void run_action(GraphInterfaceType &g, Action a,
ReverseCheck, DirectedCheck, bool run_all=false); ReverseCheck, DirectedCheck, bool run_all=false);
// useful overload for common case where all graph types should be probed // useful overload for common case where all graph types should be probed
template <class GraphInterfaceType, class Action> template <class GraphInterfaceType, class Action>
friend void run_action(GraphInterfaceType &g, Action a); friend void run_action(GraphInterfaceType &g, Action a);
friend class scalarS; friend class scalarS;
......
...@@ -519,7 +519,7 @@ class Graph(object): ...@@ -519,7 +519,7 @@ class Graph(object):
@_handle_exceptions @_handle_exceptions
@_lazy_load @_lazy_load
def set_reverse(self, reversed): def set_reverse(self, is_reversed):
"""Reverse the direction of the edges, if 'reversed' is True, or """Reverse the direction of the edges, if 'reversed' is True, or
maintain the original direction otherwise.""" maintain the original direction otherwise."""
self.__graph.SetReversed(is_reversed) self.__graph.SetReversed(is_reversed)
...@@ -802,7 +802,7 @@ class Graph(object): ...@@ -802,7 +802,7 @@ class Graph(object):
if weight == None: if weight == None:
weight = "" weight = ""
hist = self.__graph.GetSampledDistanceHistogram(weight, samples, seed) hist = self.__graph.GetSampledDistanceHistogram(weight, samples, seed)
avg, err = get_mean(dict([(1.0/k, v) for k, v in hist.iteritems()])) avg, err = _get_mean(dict([(1.0/k, v) for k, v in hist.iteritems()]))
(1.0/avg, err/(avg**2)) (1.0/avg, err/(avg**2))
@_attrs(opt_group=__groups[-1]) @_attrs(opt_group=__groups[-1])
...@@ -1013,3 +1013,113 @@ class Graph(object): ...@@ -1013,3 +1013,113 @@ class Graph(object):
if format == 'auto': if format == 'auto':
format = '' format = ''
self.__graph.GetCommunityNetwork(property, size_property, file, format) self.__graph.GetCommunityNetwork(property, size_property, file, format)
__groups.append("Plugins")
@_attrs(opt_group=__groups[-1], first_subopt="arg_names")
@_handle_exceptions
@_lazy_load
def run_action(self, code, arg_names=[], local_dict=None,
global_dict=None, force=0, compiler="gcc", verbose=0,
auto_downcast=1, support_code="", libraries=[],
library_dirs=[], extra_compile_args=[],
runtime_library_dirs=[], extra_objects=[],
extra_link_args=[]):
"""Compile (if necessary) and run the C++ action specified by 'code',
using weave."""
try:
import scipy.weave
except ImportError:
raise GraphError(self, "You need to have scipy installed to use" + \
" 'run_action'.")
# this is the code template which defines the action functor
support_code_template = """
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <graph_tool/graph.hh>
#include <graph_tool/graph_filtering.hh>
#include <graph_tool/graph_properties.hh>
using namespace boost;
using namespace boost::tuples;
using namespace std;
using namespace graph_tool;
struct action
{
template <class Graph, class VertexIndex, class EdgeIndex,
class Args>
void operator()(Graph& g, VertexIndex vertex_index,
EdgeIndex edge_index,
dynamic_properties& properties,
const Args& args) const
{
// convenience typedefs
typedef typename graph_traits<Graph>::vertex_descriptor
vertex_t;
typedef typename graph_traits<Graph>::vertex_iterator
vertex_iter_t;
typedef typename graph_traits<Graph>::edge_descriptor edge_t;
typedef typename graph_traits<Graph>::edge_iterator edge_iter_t;
// the arguments will be expanded below
%s
// the actual code
%s
}
};
"""
# each term on the expansion will properly unwrap a tuple pointer value
# to a reference with the appropriate name and type
exp_term = """typename remove_pointer<typename element<%d,Args>::type>
::type& %s = *get<%d>(args);"""
arg_expansion = "\n".join([ exp_term % (i,arg_names[i],i) for i in \
xrange(0, len(arg_names))])
support_code = support_code_template % (arg_expansion, code) + \
support_code
# insert a hash value of the support_code into the code below, to force
# recompilation when support_code changes
import hashlib
support_code_hash = hashlib.md5(support_code).hexdigest()
# the actual inline code will just call g.RunAction() on the underlying
# GraphInterface instance. The inline arguments will be packed into a
# tuple of pointers.
code = r"""
python::object pg(python::handle<>
(python::borrowed((PyObject*)(self___graph))));
GraphInterface& g = python::extract<GraphInterface&>(pg);
g.RunAction(action(), make_tuple(%s));
// %s
""" % (", ".join(["&%s" %a for a in arg_names]), support_code_hash)
# we need to get the locals and globals of the _calling_ function. We
# need to go deeper into the call stack due to all the function
# decorators being used.
call_frame = sys._getframe(5)
if local_dict is None:
local_dict = call_frame.f_locals
if global_dict is None:
global_dict = call_frame.f_globals
local_dict["self___graph"] = self.__graph # the graph interface
# call weave and pass all the updated kw arguments
scipy.weave.inline(code, ["self___graph"] + arg_names, force=force,
local_dict=local_dict, global_dict=global_dict,
compiler=compiler, verbose=verbose,
auto_downcast=auto_downcast,
support_code=support_code,
libraries=["graph_tool"] + libraries,
library_dirs=sys.path + library_dirs,
extra_compile_args=["-O3","-ftemplate-depth-150",
"-Wall", "-Wno-deprecated"] + \
extra_compile_args,
runtime_library_dirs=runtime_library_dirs,
extra_objects=extra_objects,
extra_link_args=extra_link_args)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment