1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
# SPDX-FileCopyrightInfo: Copyright © DUNE Project contributors, see file LICENSE.md in module root
# SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception
import numpy
from dune.common.hashit import hashIt
from dune.common.utility import isString
def cppType(arg):
"""
Determine the C++ type and header includes corresponding to a Python object.
Args:
arg: The Python object for which the C++ type needs to be determined.
Returns:
tuple: A tuple containing the determined C++ type and the necessary C++ include files as a list.
Raises:
Exception: If the C++ type for the given argument cannot be deduced.
Notes:
This function determines the corresponding C++ type for a given Python object.
For numpy.ndarray objects, the function determines the corresponding C++ type based on the data type of the array elements.
If the array contains elements of dtype other than int, long, std::size_t, or double, it's treated as a generic pybind11::array_t.
For tuples/lists, if all elements have the same type, they are converted to a std::vector of that type. If the elements have different types, they are converted to a std::tuple.
"""
try:
t, i = arg.cppTypeName + " &", arg.cppIncludes
except AttributeError:
if isinstance(arg, bool):
t, i = "bool", []
elif isinstance(arg, int) or isinstance(arg,numpy.intc):
t, i = "int", []
elif isinstance(arg,numpy.int_):
t, i = "long", []
elif isinstance(arg,numpy.intp):
t, i = "std::size_t", []
elif isinstance(arg, float) or isinstance(arg,numpy.float64):
t, i = "double", []
elif isinstance(arg, numpy.ndarray):
dtype = None
if arg.dtype.type == numpy.intc:
dtype="int"
elif arg.dtype.type == numpy.int_:
dtype="long"
elif arg.dtype.type == numpy.intp:
dtype="std::size_t"
elif arg.dtype.type == numpy.float64:
dtype="double"
if dtype is None:
t, i = "pybind11::array", ["dune/python/pybind11/numpy.h"]
else:
t, i = "pybind11::array_t<"+dtype+">", ["dune/python/pybind11/numpy.h"]
elif isinstance(arg, str):
t, i = "std::string", ["string"]
elif callable(arg):
t, i = "pybind11::function", ["dune/python/pybind11/pybind11.h"]
elif isinstance(arg,tuple) or isinstance(arg,list):
t, i = cppType(arg[0])
if len(arg) > 1 and cppType(arg[1])[0]!=t: # check if the second element has the same type, if not we create an std::tuple
t = "std::tuple<" + t
for a in arg[1:]:
tt, ii = cppType(a)
t += ", " + tt
i += ii
i+=["tuple"]
t += ">"
return t, i
t = "std::vector<"+t+">"
i += ["vector"]
else:
raise Exception("Cannot deduce C++ type for the following argument: " + repr(arg))
return t,i
def load(functionName, includes, *args, pythonName=None):
'''Just in time compile an algorithm.
Generates binding for a single (template) function. The name of the
function and the C++ types of the arguments passed to this function are
used to generate a static type used in the bindings. The file(s)
containing the code for the function are passed in either as single
string or as a list of strings. Note that these files will be copied
into the generated module. The file name can include a path name. So in
the simples case `includes="header.hh" will include the file from the
current working directory. To include a file from the directory
containing the calling script use
`includes=dune.generator.path(__file__)+"header.hh"`.
Args:
functionName: name of the C++ function to provide bindings for
includes: single or list of files to add to the generated module
*args: list of arguments that will be passed to the generated module
Kwargs:
pythonName: A readable name for the generated function that is used in
diagnostic messages. If this is not provided, the function
signature is used.
Returns:
Callalble object
'''
# header guard is added further down
source = '#include <config.h>\n\n'
source += '#define USING_DUNE_PYTHON 1\n\n'
if isString(includes):
with open(includes, "r") as include:
source += include.read()
source += "\n"
includes = []
elif hasattr(includes,"readable"): # for IOString
with includes as include:
source += include.read()
source += "\n"
includes = []
elif isinstance(includes, list):
for includefile in includes:
with open(includefile, "r") as include:
source += include.read()
source += "\n"
includes = []
argTypes = []
for arg in args:
t,i = cppType(arg)
argTypes.append(t)
includes += i
signature = functionName + "( " + ", ".join(argTypes) + " )"
moduleName = "algorithm_" + hashIt(signature) + "_" + hashIt(source)
# add unique header guard with moduleName
source = '#ifndef Guard_'+moduleName+'\n' + \
'#define Guard_'+moduleName+'\n\n' + \
source
includes = sorted(set(includes))
source += "".join(["#include <" + i + ">\n" for i in includes])
source += "\n"
source += '#include <dune/python/common/typeregistry.hh>\n'
source += '#include <dune/python/pybind11/pybind11.h>\n'
source += '\n'
source += "PYBIND11_MODULE( " + moduleName + ", module )\n"
source += "{\n"
source += " module.def( \"run\", [] ( " + ", ".join([argTypes[i] + " arg" + str(i) for i in range(len(argTypes))]) + " ) {\n"
source += " return " + functionName + "( " + ", ".join(["arg" + str(i) for i in range(len(argTypes))]) + " );\n"
source += " } );\n"
source += "}\n"
source += "#endif\n"
# Use signature as python name if none was expicitely provided
if not pythonName:
pythonName = signature
# make sure to reload the builder here in case it got updated
from . import builder
return builder.load(moduleName, source, pythonName).run
def run(functionName, includes, *args):
'''Just in time compile and run an algorithm.
For details see the help for `dune.algorithm.load`.
Args:
functionName: name of the C++ function to provide bindings for
includes: single or list of files to add to the generated module
*args: list of arguments that will be passed to the generated module
Returns:
return value of `functionName(*args)`
'''
return load(functionName, includes, *args)(*args)
|