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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
# 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
class CPPType:
def __init__(self,typeName,includes=[]):
self.cppTypeName = typeName
self.cppIncludes = includes
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"`.
The generated file will be compiled within the `dune-py` module and
thus all include search paths and libraries are added to the command
line. Currently we do not provide a direct way to add additional
external packages not found during the build of the Dune modules. A way
around this is to set the `CPATH` and `LIBRARY_PATH` environment
variables before calling this function.
Examples
--------
Assume that you want to use some `Eigen` linear algebra and would like
to pass a `numpy` array to a function taking an `Eigen::VectorXd`. By
default a `numpy` array will be export to C++ using `pybind11::array`
although `pybind11` can also export this as `Eigen::VectorXd`.
In addition `Dune` might not have been configured to find `Eigen` if
it not in a default position in the system. In this case the
following code will work
```
code="""
#include <dune/python/pybind11/eigen.h>
auto test(ColVec_t& x_1) {...}
"""
os.environ["CPATH"] = "PathToEigen"
testAlgo = dune.generator.algorithm.load('test', io.StringIO(code),
dune.generator.algorithm.CPPType("Eigen::VectorXd") )
```
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.
Entries here can be instances of standard Python types,
other Dune objects, or instances of the special class
`algorithm.CPPType` which is constructed by
providing a string with the required C++ type and
optionally a list of include files to add at the
top of the generated file.
Note: the type of the object passed to C++ must be
exportable.
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:
cppSource = include.read()
cppSource += "\n"
includes = []
elif hasattr(includes,"readable"): # for IOString
with includes as include:
cppSource = include.read()
cppSource += "\n"
includes = []
elif isinstance(includes, list):
cppSource = ""
for includefile in includes:
with open(includefile, "r") as include:
cppSource += include.read()
sppSource += "\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(cppSource)
# 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 += cppSource
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)
|