inline.py 11.7 KB
Newer Older
1
#! /usr/bin/env python
2
# -*- coding: utf-8 -*-
3
#
4
#
Tiago Peixoto's avatar
Tiago Peixoto committed
5
# Copyright (C) 2007-2012 Tiago de Paula Peixoto <tiago@skewed.de>
6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# 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/>.

20 21
from __future__ import division, absolute_import, print_function

22
import sys, string, hashlib, os.path, re, glob
23
from .. import *
24
from .. import libgraph_tool_core
Tiago Peixoto's avatar
Tiago Peixoto committed
25
import numpy
26
from .. dl_import import dl_flags
27
import warnings
28 29 30

try:
    import scipy.weave
31 32 33 34
except (ImportError, AttributeError) as e:
    msg = "Error importing scipy.weave module'%s'; run_action.inline() will not work!" % str(e)
    warnings.filterwarnings("always", msg, ImportWarning)
    warnings.warn(msg, ImportWarning)
35

36 37 38 39

# sys.path can be dirty and in unicode! :-p
sys_path = [str(d) for d in sys.path if os.path.isdir(d)]

Tiago Peixoto's avatar
Tiago Peixoto committed
40
prefix = None
41
for d in [p + "/graph_tool" for p in sys_path]:
Tiago Peixoto's avatar
Tiago Peixoto committed
42 43 44 45 46
    if os.path.exists(d):
        prefix = d
        break

inc_prefix = prefix + "/include"
Tiago Peixoto's avatar
Tiago Peixoto committed
47 48
cxxflags = libgraph_tool_core.mod_info().cxxflags + " -I%s" % inc_prefix + \
    " -I%s" % inc_prefix + "/boost-workaround"
49 50

# this is the code template which defines the action function object
Tiago Peixoto's avatar
Tiago Peixoto committed
51 52
support_template = open(prefix + "/run_action/run_action_support.hh").read()
code_template = open(prefix + "/run_action/run_action_template.hh").read()
53

54 55
# hash all the headers to force recompilation if code changes
headers_hash = ""
Tiago Peixoto's avatar
Tiago Peixoto committed
56 57 58 59 60 61 62
incs = glob.glob(inc_prefix + "/*")
while len(incs) > 0:
    inc = incs[0]
    del incs[0]
    if os.path.isdir(inc):
        incs += glob.glob(inc + "/*")
    else:
63
        headers_hash = hashlib.md5((headers_hash + open(inc).read()).encode('utf-8')).hexdigest()
64

65
# property map types
Tiago Peixoto's avatar
Tiago Peixoto committed
66 67 68 69 70 71 72 73
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;
"""

Tiago Peixoto's avatar
Tiago Peixoto committed
74

Tiago Peixoto's avatar
Tiago Peixoto committed
75
def clean_prop_type(t):
Tiago Peixoto's avatar
Tiago Peixoto committed
76 77 78
    return t.replace(" ", "_").replace("::", "_")\
           .replace("<", "_").replace(">", "_").\
           replace("__", "_")
Tiago Peixoto's avatar
Tiago Peixoto committed
79

80
for d in ["vertex", "edge", "graph"]:
81
    for t in value_types():
Tiago Peixoto's avatar
Tiago Peixoto committed
82
        props += "typedef %s_prop_t::as<%s >::type %sprop_%s_t;\n" % \
Tiago Peixoto's avatar
Tiago Peixoto committed
83 84
                 (d, t.replace("bool", "uint8_t"), d[0], clean_prop_type(t))

Tiago Peixoto's avatar
Tiago Peixoto committed
85 86 87

def get_graph_type(g):
    return libgraph_tool_core.get_graph_type(g._Graph__graph)
88

Tiago Peixoto's avatar
Tiago Peixoto committed
89 90

def inline(code, arg_names=None, local_dict=None,
91
           global_dict=None, force=False, compiler="gcc", verbose=False,
Tiago Peixoto's avatar
Tiago Peixoto committed
92 93 94 95
           auto_downcast=1, support_code="", libraries=None,
           library_dirs=None, extra_compile_args=None,
           runtime_library_dirs=None, extra_objects=None,
           extra_link_args=None, mask_ret=None, debug=False):
Tiago Peixoto's avatar
Tiago Peixoto committed
96 97
    """Compile (if necessary) and run the C++ code specified by 'code', using :mod:`~scipy.weave`.
    The (possibly modified) variables in 'arg_names' are returned.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

    See :func:`scipy.weave.inline` for detailed parameter documentation.

    Notes
    -----
    Graphs and property maps are automatically converted to appropriate [Boost]_
    graph types. For convenience, the graph types are automatically typedef'd to
    `${name}_graph_t`, where ${name} is the graph's variable name passed to
    `arg_names`. Property map types are typedef'd to `vprop_${val_type}_t`,
    `eprop_${val_type}_t` or `gprop_${val_type}_t`, for vertex, edge or graph
    properties, where ${val_type} specifies the value type (e.g. int, bool,
    double, etc.). In the case of vector types, the "<" and ">" symbols are
    replaced by underscores ("_").

    Examples
    --------
    >>> from numpy.random import seed
    >>> seed(42)
    >>> g = gt.random_graph(100, lambda: (3, 3))
    >>> nv = 0
    >>> ret = gt.inline("nv = num_vertices(g);", ['g', 'nv'])
119
    >>> print(ret["nv"])
120 121 122 123 124
    100
    >>> prop = g.new_vertex_property("vector<double>")
    >>> prop[g.vertex(0)] = [1.0, 4.2]
    >>> val = 0.0
    >>> ret = gt.inline("val = prop[vertex(0,g)][1];", ['g', 'prop', 'val'])
125
    >>> print(ret["val"])
126 127 128
    4.2

    References
129
    ----------
130 131 132
    .. [Boost] http://www.boost.org/libs/graph/doc/table_of_contents.html
    .. [Weave] http://www.scipy.org/Weave

133
    """
134

Tiago Peixoto's avatar
Tiago Peixoto committed
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    if arg_names == None:
        arg_names = []
    if libraries == None:
        libraries = []
    if library_dirs == None:
        library_dirs = []
    if extra_compile_args == None:
        extra_compile_args = []
    if runtime_library_dirs == None:
        runtime_library_dirs = []
    if extra_objects == None:
        extra_objects = []
    if  extra_link_args == None:
        extra_link_args = []
    if mask_ret == None:
        mask_ret = []
151 152 153 154 155

    # we need to get the locals and globals of the _calling_ function. Thus, we
    # need to go deeper into the call stack
    call_frame = sys._getframe(1)
    if local_dict is None:
Tiago Peixoto's avatar
Tiago Peixoto committed
156
        local_dict = call_frame.f_locals
157 158
    if global_dict is None:
        global_dict = call_frame.f_globals
Tiago Peixoto's avatar
Tiago Peixoto committed
159 160 161 162 163 164 165 166

    # 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:
167
        if arg not in list(local_dict.keys()) and arg not in list(global_dict.keys()):
Tiago Peixoto's avatar
Tiago Peixoto committed
168
            raise ValueError("undefined variable: " + arg)
169
        if arg in list(local_dict.keys()):
Tiago Peixoto's avatar
Tiago Peixoto committed
170 171 172
            arg_val = local_dict[arg]
        else:
            arg_val = global_dict[arg]
173
        if issubclass(type(arg_val), Graph):
Tiago Peixoto's avatar
Tiago Peixoto committed
174 175 176 177
            alias = "__gt__" + arg
            gi = "__gt__" + arg + "__gi"
            graph_type = get_graph_type(arg_val)
            gi_val = arg_val._Graph__graph
Tiago Peixoto's avatar
Tiago Peixoto committed
178 179
            arg_def += "typedef GraphWrap<%s > %s_graph_t;\n" % (graph_type, arg)
            arg_def += "GraphInterface& %s = python::extract<GraphInterface&>(%s);\n" % \
Tiago Peixoto's avatar
Tiago Peixoto committed
180 181 182 183 184
                        (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
185
        elif type(arg_val) == PropertyMap:
Tiago Peixoto's avatar
Tiago Peixoto committed
186 187 188 189 190 191 192 193 194
            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()))
Tiago Peixoto's avatar
Tiago Peixoto committed
195
            arg_def += "%s %s;\n" % (prop_name, arg)
Tiago Peixoto's avatar
Tiago Peixoto committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
            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
Tiago Peixoto's avatar
Tiago Peixoto committed
217
            arg_def += "bool %s;\n" % arg
Tiago Peixoto's avatar
Tiago Peixoto committed
218 219 220 221 222 223
            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)
224
            if arg in list(local_dict.keys()):
Tiago Peixoto's avatar
Tiago Peixoto committed
225 226 227 228 229 230 231
                alias_dict[arg] = local_dict[arg]
            else:
                alias_dict[arg] = global_dict[arg]

    # handle returned values
    return_vals = ""
    for arg in arg_names:
232
        if arg in list(local_dict.keys()):
Tiago Peixoto's avatar
Tiago Peixoto committed
233 234 235 236
            arg_val = local_dict[arg]
        else:
            arg_val = global_dict[arg]
        if arg not in mask_ret and \
237 238
               type(arg_val) not in [numpy.ndarray, PropertyMap] and \
               not issubclass(type(arg_val), Graph):
Tiago Peixoto's avatar
Tiago Peixoto committed
239 240 241 242 243 244 245 246 247 248 249
            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
250 251 252 253 254 255 256
    text = support_code + code + " ".join(libraries + library_dirs +
                                          [cxxflags] + \
                                          extra_compile_args +\
                                          extra_objects + \
                                          extra_link_args) + \
                                          headers_hash + __version__
    support_hash = hashlib.md5(text.encode("ascii")).hexdigest()
Tiago Peixoto's avatar
Tiago Peixoto committed
257 258 259 260
    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)
261 262 263 264 265

    # RTLD_GLOBAL needs to be set in dlopen() if we want typeinfo and
    # friends to work properly across DSO boundaries. See
    # http://gcc.gnu.org/faq.html#dso
    orig_dlopen_flags = sys.getdlopenflags()
266
    sys.setdlopenflags(dl_flags)
267 268 269

    # call weave and pass all the updated kw arguments
    ret_vals = \
270
             scipy.weave.inline(inline_code, arg_alias, force=int(force),
Tiago Peixoto's avatar
Tiago Peixoto committed
271
                                local_dict=alias_dict, global_dict=global_dict,
272
                                compiler=compiler, verbose=int(verbose),
273 274 275
                                auto_downcast=auto_downcast,
                                support_code=support_code,
                                libraries=libraries,
276
                                library_dirs=sys_path + library_dirs,
Tiago Peixoto's avatar
Tiago Peixoto committed
277
                                extra_compile_args=compile_args,
278 279
                                runtime_library_dirs=runtime_library_dirs,
                                extra_objects=extra_objects,
Tiago Peixoto's avatar
Tiago Peixoto committed
280
                                extra_link_args=["-Wl,-E "] + extra_link_args)
281 282 283
    # check if exception was thrown
    if ret_vals["__exception_thrown"]:
        libgraph_tool_core.raise_error(ret_vals["__exception_error"])
284 285 286
    else:
        del ret_vals["__exception_thrown"]
        del ret_vals["__exception_error"]
Tiago Peixoto's avatar
Tiago Peixoto committed
287 288
    sys.setdlopenflags(orig_dlopen_flags)  # reset dlopen to normal case to
                                           # avoid unnecessary symbol collision
Tiago Peixoto's avatar
Tiago Peixoto committed
289 290
    # set return vals
    for arg in arg_names:
Tiago Peixoto's avatar
Tiago Peixoto committed
291 292
        if arg in ret_vals:
            if arg in local_dict:
Tiago Peixoto's avatar
Tiago Peixoto committed
293 294 295
                local_dict[arg] = ret_vals[arg]
            else:
                global_dict[arg] = ret_vals[arg]
Tiago Peixoto's avatar
Tiago Peixoto committed
296
    return ret_vals
297