Skip to content
Snippets Groups Projects
Commit 52eaab16 authored by Carsten Gräser's avatar Carsten Gräser
Browse files

Merge branch 'feature/PythonBindingsTV' into 'master'

[Python] add Python bindings for tuplevector

See merge request !1367
parents 60580459 ced1c1ef
Branches
Tags
1 merge request!1367[Python] add Python bindings for tuplevector
Pipeline #72238 waiting for manual action
......@@ -15,6 +15,12 @@ In order to build the DUNE core modules you need at least the following software
## Changelog
- Python: Add `TupleVector` Python bindings
- Python: The function `cppType` now support Python tuples, which are converted to the C++ type `std::tuple`
- `TupleVector` now implements the standard protocol for tuple-like types.
- There is a new base class `IteratorFacade` that unifies `ForwardIteratorFacade`,
`BidirectionalIteratorFacade`, `RandomAccessIteratorFacade` by making the iterator
category a template. Furthermore the new `IteratorFacade` class allows to specify
......
......@@ -98,4 +98,26 @@ constexpr auto makeTupleVector(T&&... t)
} // namespace Dune
namespace std
{
/** \brief Make std::tuple_element work for TupleVector
*
* It derives from std::tuple after all.
*/
template <size_t i, typename... Args>
struct tuple_element<i,Dune::TupleVector<Args...> >
{
using type = typename std::tuple_element<i, std::tuple<Args...> >::type;
};
/** \brief Make std::tuple_size work for TupleVector
*
* It derives from std::tuple after all.
*/
template <typename... Args>
struct tuple_size<Dune::TupleVector<Args...> >
: std::integral_constant<std::size_t, sizeof...(Args)>
{};
}
#endif // DUNE_COMMON_TUPLEVECTOR_HH
// SPDX-FileCopyrightText: Copyright © DUNE Project contributors, see file LICENSE.md in module root
// SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception
#ifndef DUNE_PYTHON_COMMON_TVECTOR_HH
#define DUNE_PYTHON_COMMON_TVECTOR_HH
/**
* @file tuplevector.hh
* @brief Python bindings for TupleVector.
*/
#include <dune/python/pybind11/pybind11.h>
#include <dune/python/pybind11/cast.h>
#include <dune/common/tuplevector.hh>
#include <dune/common/hybridutilities.hh>
namespace Dune {
namespace Python {
/**
* @brief Register TupleVector bindings.
*
* This function registers Python bindings for TupleVector.
*
* @tparam TV The type of the TupleVector.
* @tparam options Additional options for pybind11 class.
* @param scope The scope to register the bindings.
* @param cls The pybind11 class to register the bindings.
*/
template <class TV, class... options>
void registerTupleVector(pybind11::handle scope, pybind11::class_<TV, options...> cls) {
namespace py = pybind11;
using py::operator"" _a;
cls.def(py::init([](py::tuple x) {
assert(std::tuple_size_v<TV> == x.size());
return Dune::unpackIntegerSequence([&](auto... i) {
return new TV((x[i].template cast<std::tuple_element_t<i, TV> >())...); },
std::make_index_sequence<std::tuple_size_v<TV> >{});
}));
cls.def("assign", [](TV &self, const TV &x) { self = x; }, "x"_a);
cls.def("copy", [](const TV &self) { return new TV(self); });
cls.def("__getitem__", [](const TV &self, size_t index) {
if (index >= self.size())
throw py::index_error();
return Dune::Hybrid::switchCases(Dune::Hybrid::integralRange(Dune::index_constant<std::tuple_size_v<TV> >()),index, [&](auto i) {
return py::cast(self[i],py::return_value_policy::reference);
},[]() {return py::object{};});
},py::return_value_policy::reference_internal);
cls.def("__setitem__", [&](TV &self, size_t index, const py::object &value) {
if (index >= self.size())
throw py::index_error();
Dune::Hybrid::switchCases(Dune::Hybrid::integralRange(Dune::index_constant<std::tuple_size_v<TV> >()),index, [&](auto i) {
try
{
self[i] = value.cast<std::tuple_element_t<i, TV>>();
}
catch(const py::cast_error & e)
{
std::cerr << "Your provide value is not of the correct type, which should be " << Dune::className<std::tuple_element_t<i, TV>>()<<" for your provided index "<< index<< std::endl;
throw e;
}
});
});
cls.def("__len__", [](const TV &self) { return self.size(); });
}
} // namespace Python
} // namespace Dune
#endif // #ifndef DUNE_PYTHON_COMMON_TVECTOR_HH
......@@ -8,6 +8,12 @@ dune_python_add_test(NAME pythontests
LABELS quick
)
dune_python_add_test(NAME tuplevectortest
SCRIPT tuplevectortest.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
LABELS quick
)
dune_add_test(SOURCES test_embed1.cc
LINK_LIBRARIES ${DUNE_LIBS} ${Python3_LIBRARIES}
LABELS quick
......
# SPDX-FileCopyrightText: Copyright © DUNE Project contributors, see file LICENSE.md in module root
# SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception
from io import StringIO
import os,sys
os.environ['DUNE_LOG_LEVEL'] = 'debug'
os.environ['DUNE_SAVE_BUILD'] = 'console'
from dune.common import FieldVector,TupleVector
tuple1 = (17.0, FieldVector([2,2]), 3.0, FieldVector([1, 2, 3]))
mtbv= TupleVector(tuple1)
for v in range(len(mtbv)-1): # block vector has no correct comparison by itself
assert(mtbv[v] == tuple1[v])
mtbv[1]= FieldVector([2,5]) # check assignment of field vector to multi type block vector entry
# check that is really the same data address
assert(sys.getsizeof(mtbv)==7*8)
mtbv2= TupleVector(18.0, mtbv)# try nested TupleVectors
assert(mtbv2[0] == 18)
for v in range(len(mtbv2[1])-1): # block vector has no correct comparison by itself
if v == 1:
assert(mtbv2[1][v] == FieldVector([2,5])) # check if field vector was correctly assigned
else:
assert(mtbv2[1][v] == tuple1[v])
tuple3 = (34.0, FieldVector([4,10]), 6.0, FieldVector([2, 4, 6]))
for v in range(len(mtbv)-1):
mtbv[v]+=mtbv[v]
for v in range(len(mtbv)-1): # block vector has no correct comparison by itself
assert(mtbv[v] == tuple3[v])
mtbv3 = TupleVector(tuple1,allowByReference=True)
tuple1[1][0]= 5
assert(mtbv3[1][0] == 5) # check if field vector was correctly assigned by reference
try: #assignment of scalar to field vector should throw
mtbv[3]= 2.0
except RuntimeError:
pass
tuple4 = ( FieldVector([1,2,3]), FieldVector([1, 2]))
mtbv4 = TupleVector(tuple4,allowByReference=False)
mtbv5 = TupleVector(tuple4,allowByReference=True)
runCode="""
#include <dune/common/tuplevector.hh>
#include <dune/common/fvector.hh>
#include <iostream>
#include <dune/python/pybind11/cast.h>
double run(const Dune::TupleVector<Dune::FieldVector<double,3>,Dune::FieldVector<double,2>> &t) {
double res=0;
for (int i=0;i<3;i++) res+=std::get<0>(t)[i];
for (int i=0;i<2;i++) res+=std::get<1>(t)[i];
return res;
}
void run2( Dune::TupleVector<Dune::FieldVector<double,3>,Dune::FieldVector<double,2>> &t) {
std::get<0>(t)[0]=13;
}
"""
from dune.generator.algorithm import run
tup = (FieldVector([1, 2,3]), FieldVector([1, 2]))
assert (run("run",StringIO(runCode),tup)==9)
assert (run("run",StringIO(runCode),mtbv4)==9)
run("run2",StringIO(runCode),mtbv4)
assert (mtbv4[0][0]==13)
from dune.generator.exceptions import CompileError
try:
run("run2",StringIO(runCode),tup)
except CompileError:
pass
......@@ -105,3 +105,63 @@ def FieldVector(values):
# def FieldMatrix(values):
# fm = "FieldMatrix_" + str(len(values)) + "_" + str(len(values[0]))
# return globals()[fm](values)
def _cppTypesFromTuple(tup,tupleNameWrapper,allowByReference):
from dune.generator.algorithm import cppType
"""
Converts Python types into C++ types for TupleVector.
Args:
tup (tuple): A tuple representing the Python types.
allowByReference (bool): Indicates whether the deduced type of C++ objects should be a (mutable) reference.
Returns:
str: The C++ type string representing the TupleVector.
list: A list of includes needed for the C++ types.
"""
typeName= tupleNameWrapper+"<"
includes=[]
for arg in tup:
ti,i = cppType(arg)
includes+= i
if not allowByReference:
ti=ti.rstrip("&") # remove trailing reference from c++ type
typeName+= ti+ ","
typeName = typeName.rstrip(",") + " >"
return typeName,includes
def TupleVector(*args, allowByReference=False):
from dune.common.hashit import hashIt
"""
Creates a TupleVector object.
This function creates a TupleVector object based on the provided arguments.
Args:
*args: Variable-length argument list representing the types of TupleVector.
You can pass several objects or pass in a single tuple of objects.
allowByReference (bool, optional): Indicates whether to allow storing reference to C++ objects inside the TupleVector.
Returns:
TupleVector: A TupleVector object.
"""
includes = []
typeName= ""
if len(args)==1 and isinstance(args,tuple):
typeName,includes= _cppTypesFromTuple(*args,"Dune::TupleVector",allowByReference)
stdTupleType,_= _cppTypesFromTuple(*args,"std::tuple",allowByReference)
else:
typeName,includes= _cppTypesFromTuple(args,"Dune::TupleVector",allowByReference)
stdTupleType,_= _cppTypesFromTuple(args,"std::tuple",allowByReference)
includes+= ["dune/python/common/tuplevector.hh"]
typeHash = "tuplevector_" + hashIt(typeName)
from dune.generator.generator import SimpleGenerator
generatorMTBV =SimpleGenerator("TupleVector","Dune::Python")
if len(args)==1 and isinstance(args,tuple):
return generatorMTBV.load(includes ,typeName ,typeHash, baseClasses=[stdTupleType] ).TupleVector(*args)
else:
return generatorMTBV.load(includes ,typeName ,typeHash, baseClasses=[stdTupleType] ).TupleVector(args)
......@@ -7,6 +7,27 @@ 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:
......@@ -40,6 +61,15 @@ def cppType(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:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment