Commit 0c87c492 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Improve run_action module

Now inline() automatically converts known variables (such as property
maps) and defaults to boost::python objects, instead of scxx objects.

The code now is only generated for the current filtered, reversed and/or
directed status of the graph, reducing compile time and binary size.

Edge modification (remove and add) is now protected by a GraphWrap
class, which takes care of the edge index housekeeping, which does not
need to be done by hand anymore.

The function is also no longer bound to one graph, and can take an
arbitrary number of variables of known or unknown types, including
graphs.
parent ba56ebd9
...@@ -35,6 +35,7 @@ libgraph_tool_core_la_SOURCES = \ ...@@ -35,6 +35,7 @@ libgraph_tool_core_la_SOURCES = \
libgraph_tool_core_la_includedir = $(pythondir)/graph_tool/include libgraph_tool_core_la_includedir = $(pythondir)/graph_tool/include
libgraph_tool_core_la_include_HEADERS = \ libgraph_tool_core_la_include_HEADERS = \
graph_adaptor.hh \ graph_adaptor.hh \
graph_wrap.hh \
graph_filtering.hh \ graph_filtering.hh \
graph.hh \ graph.hh \
graph_properties.hh \ graph_properties.hh \
......
...@@ -158,20 +158,17 @@ public: ...@@ -158,20 +158,17 @@ public:
void AddEdgeIndex(const edge_t& e); void AddEdgeIndex(const edge_t& e);
void RemoveEdgeIndex(const edge_t& e); void RemoveEdgeIndex(const edge_t& e);
private:
// Gets the encapsulated graph view. See graph_filtering.cc for details // Gets the encapsulated graph view. See graph_filtering.cc for details
boost::any GetGraphView() const; boost::any GetGraphView() const;
private:
// Generic graph_action functor. See graph_filtering.hh for details. // Generic graph_action functor. See graph_filtering.hh for details.
template <class Action, template <class Action,
class TR1=boost::mpl::vector<>, class TR2=boost::mpl::vector<>, class TR1=boost::mpl::vector<>, class TR2=boost::mpl::vector<>,
class TR3=boost::mpl::vector<>, class TR4=boost::mpl::vector<> > class TR3=boost::mpl::vector<>, class TR4=boost::mpl::vector<> >
friend struct detail::graph_action; friend struct detail::graph_action;
// Arbitrary code execution
template <class Action>
friend void RunAction(GraphInterface& g, const Action& a);
// python interface // python interface
friend class PythonVertex; friend class PythonVertex;
template <class Graph> template <class Graph>
......
...@@ -270,6 +270,24 @@ python::list get_property_types() ...@@ -270,6 +270,24 @@ python::list get_property_types()
return plist; return plist;
} }
struct graph_type_name
{
template <class Graph>
void operator()(const Graph* gp, string& name) const
{
using python::detail::gcc_demangle;
name = string(gcc_demangle(typeid(Graph).name()));
}
};
string get_graph_type(GraphInterface& g)
{
string name;
run_action<>()(g, lambda::bind<void>(graph_type_name(), lambda::_1,
lambda::var(name)))();
return name;
}
BOOST_PYTHON_MODULE(libgraph_tool_core) BOOST_PYTHON_MODULE(libgraph_tool_core)
{ {
// numpy // numpy
...@@ -362,5 +380,7 @@ BOOST_PYTHON_MODULE(libgraph_tool_core) ...@@ -362,5 +380,7 @@ BOOST_PYTHON_MODULE(libgraph_tool_core)
.add_property("cxxflags", &LibInfo::GetCXXFLAGS) .add_property("cxxflags", &LibInfo::GetCXXFLAGS)
.add_property("install_prefix", &LibInfo::GetInstallPrefix) .add_property("install_prefix", &LibInfo::GetInstallPrefix)
.add_property("python_dir", &LibInfo::GetPythonDir); .add_property("python_dir", &LibInfo::GetPythonDir);
def("get_graph_type", &get_graph_type);
} }
// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2007 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 GRAPH_WRAP_HH
#define GRAPH_WRAP_HH
#include<utility>
// Graph wrapper which takes care of edge index housekeeping
namespace boost
{
using namespace graph_tool;
template <class Graph>
class GraphWrap
{
public:
GraphWrap(Graph& g, GraphInterface& gi)
: _g(g), _gi(gi) {}
typedef typename Graph::vertex_property_type vertex_property_type;
typedef typename Graph::edge_property_type edge_property_type;
typedef typename Graph::graph_tag graph_tag;
typedef Graph orig_graph_t;
Graph& _g;
GraphInterface& _gi;
};
template <class Graph>
GraphWrap<Graph> graph_wrap(Graph& g, GraphInterface& gi)
{
return GraphWrap<Graph>(g, gi);
}
template <class Graph>
struct graph_traits<GraphWrap<Graph> >: public graph_traits<Graph> {};
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::vertex_descriptor
source(typename graph_traits<GraphWrap<Graph> >::edge_descriptor e,
const GraphWrap<Graph>& g)
{
return source(e, g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::vertex_descriptor
target(typename graph_traits<GraphWrap<Graph> >::edge_descriptor e,
const GraphWrap<Graph>& g)
{
return target(e, g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::vertex_descriptor
vertex(typename graph_traits<GraphWrap<Graph> >::vertices_size_type n,
const GraphWrap<Graph>& g)
{
return vertex(n, g._g);
}
template <class Graph>
inline std::pair<typename graph_traits<GraphWrap<Graph> >::vertex_iterator,
typename graph_traits<GraphWrap<Graph> >::vertex_iterator>
vertices(const GraphWrap<Graph>& g)
{
return vertices(g._g);
}
template <class Graph>
inline std::pair<typename graph_traits<GraphWrap<Graph> >::edge_iterator,
typename graph_traits<GraphWrap<Graph> >::edge_iterator>
edges(const GraphWrap<Graph>& g)
{
return edges(g._g);
}
template <class Graph>
inline std::pair<typename graph_traits<GraphWrap<Graph> >::out_edge_iterator,
typename graph_traits<GraphWrap<Graph> >::out_edge_iterator >
out_edges(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
const GraphWrap<Graph>& g)
{
return out_edges(u, g._g);
}
template <class Graph>
inline std::pair<typename graph_traits<GraphWrap<Graph> >::in_edge_iterator,
typename graph_traits<GraphWrap<Graph> >::in_edge_iterator >
in_edges(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
const GraphWrap<Graph>& g)
{
return in_edges(u, g._g);
}
template <class Graph>
inline
std::pair<typename graph_traits<GraphWrap<Graph> >::adjacency_iterator,
typename graph_traits<GraphWrap<Graph> >::adjacency_iterator>
adjacent_vertices
(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
const GraphWrap<Graph>& g)
{
return adjacent_vertices(u, g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::vertices_size_type
num_vertices(const GraphWrap<Graph>& g)
{
return num_vertices(g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::edges_size_type
num_edges(const GraphWrap<Graph>& g)
{
return g._gi.GetNumberOfEdges();
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::degree_size_type
out_degree(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
const GraphWrap<Graph>& g)
{
return out_degree(u, g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::degree_size_type
in_degree(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
const GraphWrap<Graph>& g)
{
return in_degree(u, g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::degree_size_type
degree(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
const GraphWrap<Graph>& g)
{
return degree(u, g._g);
}
template <class Graph>
inline typename graph_traits<GraphWrap<Graph> >::vertex_descriptor
add_vertex(GraphWrap<Graph>& g)
{
return add_vertex(g._g);
}
template <class Graph>
inline void clear_vertex(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
GraphWrap<Graph>& g)
{
clear_vertex(u, g._g);
}
template <class Graph>
inline void remove_vertex(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
GraphWrap<Graph>& g)
{
remove_vertex(u, g._g);
}
template <class Graph>
inline std::pair<typename graph_traits<GraphWrap<Graph> >::edge_descriptor, bool>
add_edge(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
typename graph_traits<GraphWrap<Graph> >::vertex_descriptor v,
GraphWrap<Graph>& g)
{
std::pair<typename graph_traits<GraphWrap<Graph> >::edge_descriptor, bool> retval =
add_edge(u, v, g._g);
g._gi.AddEdgeIndex(retval.first);
return retval;
}
template <class Graph>
inline void remove_edge(typename graph_traits<GraphWrap<Graph> >::edge_descriptor e,
GraphWrap<Graph>& g)
{
g._gi.RemoveEdge(e);
}
template <class Graph>
inline void remove_edge(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
typename graph_traits<GraphWrap<Graph> >::vertex_descriptor v,
Graph& g)
{
vector<typename graph_traits<GraphWrap<Graph> >::edge_descriptor> removed_edges;
typename graph_traits<GraphWrap<Graph> >::out_edge_iterator e, e_end;
for(tie(e, e_end) = out_edges(u, g); e != e_end; ++e)
if (target(e, g) == v)
removed_edges.push_back(*e);
for (typeof(removed_edges.begin()) iter = removed_edges.begin();
iter != removed_edges.end(); ++iter)
remove_edge(*iter, g);
}
template <class Graph, class Predicate>
inline void
remove_out_edge_if(typename graph_traits<GraphWrap<Graph> >::vertex_descriptor u,
Predicate predicate, Graph& g)
{
vector<typename graph_traits<GraphWrap<Graph> >::edge_descriptor> removed_edges;
typename graph_traits<GraphWrap<Graph> >::out_edge_iterator e, e_end;
for(tie(e, e_end) = out_edges(u, g); e != e_end; ++e)
if (predicate(*e))
removed_edges.push_back(*e);
for (typeof(removed_edges.begin()) iter = removed_edges.begin();
iter != removed_edges.end(); ++iter)
remove_edge(*iter, g);
}
}
#endif // GRAPH_WRAP_HH
...@@ -13,7 +13,9 @@ graph_tooldir = $(MOD_DIR) ...@@ -13,7 +13,9 @@ graph_tooldir = $(MOD_DIR)
graph_tool_run_action_PYTHON = \ graph_tool_run_action_PYTHON = \
run_action/__init__.py \ run_action/__init__.py \
run_action/inline.py \ run_action/inline.py \
run_action/run_action_support.hh \
run_action/run_action_template.hh run_action/run_action_template.hh
graph_tool_run_actiondir = $(MOD_DIR)/run_action graph_tool_run_actiondir = $(MOD_DIR)/run_action
graph_tool_test_PYTHON = \ graph_tool_test_PYTHON = \
......
...@@ -15,9 +15,10 @@ ...@@ -15,9 +15,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, string, hashlib, os.path import sys, string, hashlib, os.path, re
from .. import core from .. import core
from .. import libgraph_tool_core from .. import libgraph_tool_core
import numpy
try: try:
import scipy.weave import scipy.weave
...@@ -35,32 +36,40 @@ inc_prefix = prefix + "/include" ...@@ -35,32 +36,40 @@ inc_prefix = prefix + "/include"
cxxflags = libgraph_tool_core.mod_info().cxxflags + " -I%s" % inc_prefix cxxflags = libgraph_tool_core.mod_info().cxxflags + " -I%s" % inc_prefix
# this is the code template which defines the action function object # this is the code template which defines the action function object
support_template = open(prefix + "/run_action/run_action_template.hh").read() support_template = open(prefix + "/run_action/run_action_support.hh").read()
code_template = open(prefix + "/run_action/run_action_template.hh").read()
# property map types # property map types
props = "" props = """
typedef GraphInterface::vertex_index_map_t vertex_index_t;
typedef GraphInterface::edge_index_map_t edge_index_t;
typedef prop_bind_t<GraphInterface::vertex_index_map_t> vertex_prop_t;
typedef prop_bind_t<GraphInterface::edge_index_map_t> edge_prop_t;
typedef prop_bind_t<ConstantPropertyMap<size_t,graph_property_tag> > graph_prop_t;
"""
def clean_prop_type(t):
return t.replace(" ","_").replace("::","_")\
.replace("<","_").replace(">","_").\
replace("__","_")
for d in ["vertex", "edge", "graph"]: for d in ["vertex", "edge", "graph"]:
for t in core.value_types(): for t in core.value_types():
props += (("typedef typename %s_prop_t::template as<%s >::type" + \ props += "typedef %s_prop_t::as<%s >::type %sprop_%s_t;\n" % \
" %sprop_%s_t;\n") % \ (d,t,d[0],clean_prop_type(t))
(d,t,d[0],t.replace(" ","_").replace("::","_")\
.replace("<","_").replace(">","_"))).\ def get_graph_type(g):
replace("__","_") return libgraph_tool_core.get_graph_type(g._Graph__graph)
def inline(g, code, arg_names=[], local_dict=None, def inline(code, arg_names=[], local_dict=None,
global_dict=None, force=0, compiler="gcc", verbose=0, global_dict=None, force=0, compiler="gcc", verbose=0,
auto_downcast=1, support_code="", libraries=[], auto_downcast=1, support_code="", libraries=[],
library_dirs=[], extra_compile_args=[], library_dirs=[], extra_compile_args=[],
runtime_library_dirs=[], extra_objects=[], runtime_library_dirs=[], extra_objects=[],
extra_link_args=[], mask_ret=[]): extra_link_args=[], mask_ret=[], debug=False):
"""Compile (if necessary) and run the C++ action specified by 'code', """Compile (if necessary) and run the C++ action specified by 'code',
using weave.""" using weave."""
# we need to have different template names for each actions, to avoid
# strange RTTI issues. We'll therefore append an md5 hash of the code (plus
# grah_tool version string) to each action's name
code_hash = hashlib.md5(code + core.__version__).hexdigest()
# each term on the expansion will properly unwrap a tuple pointer value # each term on the expansion will properly unwrap a tuple pointer value
# to a reference with the appropriate name and type # to a reference with the appropriate name and type
exp_term = """typename boost::remove_pointer<typename tr1::tuple_element<%d,Args>::type>::type& %s = exp_term = """typename boost::remove_pointer<typename tr1::tuple_element<%d,Args>::type>::type& %s =
...@@ -68,36 +77,6 @@ def inline(g, code, arg_names=[], local_dict=None, ...@@ -68,36 +77,6 @@ def inline(g, code, arg_names=[], local_dict=None,
arg_expansion = "\n".join([ exp_term % (i,arg_names[i],i) for i in \ arg_expansion = "\n".join([ exp_term % (i,arg_names[i],i) for i in \
xrange(0, len(arg_names))]) xrange(0, len(arg_names))])
# handle returned values
return_vals = ""
for arg in arg_names:
if arg not in mask_ret:
return_vals += 'return_vals["%s"] = %s;\n' % (arg, arg)
support_template = string.Template(globals()["support_template"])
support_code += support_template.substitute(code_hash=code_hash,
property_map_types=props,
arg_expansion=arg_expansion,
code=code,
return_vals = return_vals)
# insert a hash value of the support_code into the code below, to force
# recompilation when support_code (and module version) changes
support_hash = hashlib.md5(support_code + core.__version__).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 = string.Template(r"""
python::object pg(python::handle<>
(python::borrowed((PyObject*)(self___graph))));
GraphInterface& g = python::extract<GraphInterface&>(pg);
RunAction(g, make_action(tr1::make_tuple(${args}), return_val));
// support code hash: ${support_hash}
""").substitute(args=", ".join(["&%s" %a for a in arg_names]),
code_hash=code_hash, support_hash=support_hash)
# we need to get the locals and globals of the _calling_ function. Thus, we # we need to get the locals and globals of the _calling_ function. Thus, we
# need to go deeper into the call stack # need to go deeper into the call stack
call_frame = sys._getframe(1) call_frame = sys._getframe(1)
...@@ -105,7 +84,108 @@ def inline(g, code, arg_names=[], local_dict=None, ...@@ -105,7 +84,108 @@ def inline(g, code, arg_names=[], local_dict=None,
local_dict = call_frame.f_locals local_dict = call_frame.f_locals
if global_dict is None: if global_dict is None:
global_dict = call_frame.f_globals global_dict = call_frame.f_globals
local_dict["self___graph"] = g._Graph__graph # the graph interface
# convert variables to boost::python::object, except some known convertible
# types
arg_def = props
arg_conv = ""
arg_alias = []
alias_dict = {}
for arg in arg_names:
if arg not in local_dict.keys() and arg not in global_dict.keys():
raise ValueError("undefined variable: "+ arg)
if arg in local_dict.keys():
arg_val = local_dict[arg]
else:
arg_val = global_dict[arg]
if issubclass(type(arg_val), core.Graph):
alias = "__gt__" + arg
gi = "__gt__" + arg + "__gi"
graph_type = get_graph_type(arg_val)
gi_val = arg_val._Graph__graph
arg_def += "typedef GraphWrap<%s > %s_graph_t;\n" % (graph_type, arg);
arg_def += "GraphInterface& %s = python::extract<GraphInterface&>(%s);\n" %\
(gi, alias)
arg_def += "%s_graph_t %s = graph_wrap(*boost::any_cast<%s*>(%s.GetGraphView()), %s);\n" % \
(arg, arg, graph_type, gi, gi)
arg_alias.append(alias)
alias_dict[alias] = gi_val
elif type(arg_val) == core.PropertyMap:
alias = "__gt__" + arg
if arg_val == arg_val.get_graph().vertex_index:
prop_name = "GraphInterface::vertex_index_map_t"
elif arg_val == arg_val.get_graph().edge_index:
prop_name = "GraphInterface::edge_index_map_t"
else:
prop_name = "%sprop_%s_t" % \
(arg_val.key_type(),
clean_prop_type(arg_val.value_type()))
arg_def += "%s %s;\n" % (prop_name, arg)
arg_conv += "%s = get_prop<%s>(%s);\n" % \
(arg, prop_name, alias)
arg_alias.append(alias)
alias_dict[alias] = arg_val
elif type(arg_val) not in [int, bool, float, string, numpy.ndarray]:
alias = "__gt__" + arg
obj_type = "python::object"
if type(arg_val) == list:
obj_type = "python::list"
elif type(arg_val) == dict:
obj_type = "python::dict"
elif type(arg_val) == tuple:
obj_type = "python::tuple"
arg_def += "%s %s;\n" % (obj_type, arg)
arg_conv += "%s = %s(python::object(python::handle<>" % (arg, obj_type) + \
"(python::borrowed((PyObject*)(%s)))));\n" % alias
arg_alias.append(alias)
alias_dict[alias] = arg_val
elif type(arg_val) == bool:
#weave is dumb with bools
alias = "__gt__" + arg
arg_def += "bool %s;\n" % arg;
arg_conv += "%s = python::extract<bool>(python::object(python::handle<>" % arg + \
"(python::borrowed((PyObject*)(%s)))));\n" % alias
arg_alias.append(alias)
alias_dict[alias] = arg_val
else:
arg_alias.append(arg)
if arg in local_dict.keys():
alias_dict[arg] = local_dict[arg]
else:
alias_dict[arg] = global_dict[arg]
# handle returned values
return_vals = ""
for arg in arg_names:
if arg in local_dict.keys():
arg_val = local_dict[arg]
else:
arg_val = global_dict[arg]
if arg not in mask_ret and \
type(arg_val) not in [numpy.ndarray, core.PropertyMap] and \
not issubclass(type(arg_val), core.Graph):
return_vals += 'return_vals["%s"] = %s;\n' % (arg, arg)
support_code += globals()["support_template"]
# set debug flag and disable optimization in debug mode
compile_args = [cxxflags] + extra_compile_args
if debug:
compile_args = [re.sub("-O[^ ]*", "", x) for x in compile_args] + ["-g"]
# insert a hash value into the code below, to force recompilation when
# support_code (and module version) changes
support_hash = hashlib.md5(support_code + code + \
" ".join(libraries + library_dirs +
[cxxflags] + \
extra_compile_args +\
extra_objects + \
extra_link_args) + \
core.__version__).hexdigest()
code += "\n// support code hash: " + support_hash
inline_code = string.Template(globals()["code_template"]).\
substitute(var_defs=arg_def, var_extract=arg_conv,
code=code, return_vals=return_vals)
# RTLD_GLOBAL needs to be set in dlopen() if we want typeinfo and # RTLD_GLOBAL needs to be set in dlopen() if we want typeinfo and
# friends to work properly across DSO boundaries. See # friends to work properly across DSO boundaries. See
...@@ -115,18 +195,17 @@ def inline(g, code, arg_names=[], local_dict=None, ...@@ -115,18 +195,17 @@ def inline(g, code, arg_names=[], local_dict=None,
# call weave and pass all the updated kw arguments # call weave and pass all the updated kw arguments
ret_vals = \ ret_vals = \
scipy.weave.inline(code, ["self___graph"] + arg_names, force=force, scipy.weave.inline(inline_code, arg_alias, force=force,
local_dict=local_dict, global_dict=global_dict, local_dict=alias_dict, global_dict=global_dict,
compiler=compiler, verbose=verbose,