diff --git a/cmake b/cmake index 094834088ad9de32f1abdc2d56d29bca2190772c..d43ba7b63f2ead6899665156eecfba96c9078239 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 094834088ad9de32f1abdc2d56d29bca2190772c +Subproject commit d43ba7b63f2ead6899665156eecfba96c9078239 diff --git a/include/eigenpy/numpy.hpp b/include/eigenpy/numpy.hpp index bd68b7c04fded250a0a71075810a55d632517e45..8abb4c99a092d3e5e0e328dd85f0c8591340f98a 100644 --- a/include/eigenpy/numpy.hpp +++ b/include/eigenpy/numpy.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2020 INRIA + * Copyright 2020-2021 INRIA */ #ifndef __eigenpy_numpy_hpp__ @@ -43,7 +43,13 @@ namespace eigenpy template <> struct NumpyEquivalentType< std::complex<long double> > { enum { type_code = NPY_CLONGDOUBLE };}; template <> struct NumpyEquivalentType<bool> { enum { type_code = NPY_BOOL };}; template <> struct NumpyEquivalentType<int> { enum { type_code = NPY_INT };}; - template <> struct NumpyEquivalentType<long> { enum { type_code = NPY_LONG };}; + template <> struct NumpyEquivalentType<unsigned int> { enum { type_code = NPY_UINT };}; +#if __APPLE__ + template <> struct NumpyEquivalentType<long> { enum { type_code = NPY_INT64 };}; +#endif + template <> struct NumpyEquivalentType<unsigned long> { enum { type_code = NPY_ULONG };}; + template <> struct NumpyEquivalentType<int64_t> { enum { type_code = NPY_INT64 };}; +// template <> struct NumpyEquivalentType<long long> { enum { type_code = NPY_LONGLONG };}; template<typename Scalar> bool isNumpyNativeType() @@ -77,6 +83,8 @@ namespace eigenpy EIGENPY_DLLAPI int call_PyArray_RegisterCanCast(PyArray_Descr *descr, int totype, NPY_SCALARKIND scalar); EIGENPY_DLLAPI PyArray_Descr * call_PyArray_MinScalarType(PyArrayObject *arr); + + EIGENPY_DLLAPI int call_PyArray_RegisterCastFunc(PyArray_Descr* descr, int totype, PyArray_VectorUnaryFunc* castfunc); } #else #define call_PyArray_Check(py_obj) PyArray_Check(py_obj) @@ -89,6 +97,7 @@ namespace eigenpy #define call_PyArray_InitArrFuncs(funcs) PyArray_InitArrFuncs(funcs) #define call_PyArray_RegisterDataType(dtype) PyArray_RegisterDataType(dtype) #define call_PyArray_RegisterCanCast(descr,totype,scalar) PyArray_RegisterCanCast(descr,totype,scalar) + #define call_PyArray_RegisterCastFunc(descr,totype,castfunc) PyArray_RegisterCastFunc(descr,totype,castfunc) #endif #endif // ifndef __eigenpy_numpy_hpp__ diff --git a/include/eigenpy/register.hpp b/include/eigenpy/register.hpp index 2164449357dfff90a177066fe6ce7e746bbbd284..516d44c45675572cd7aef01c489751740d35604b 100644 --- a/include/eigenpy/register.hpp +++ b/include/eigenpy/register.hpp @@ -56,6 +56,20 @@ namespace eigenpy } } + template<typename Scalar> + static PyArray_Descr * getPyArrayDescr() + { + namespace bp = boost::python; + if(!isNumpyNativeType<Scalar>()) + { + return getPyArrayDescr(getPyType<Scalar>()); + } + else + { + return call_PyArray_DescrFromType(NumpyEquivalentType<Scalar>::type_code); + } + } + template<typename Scalar> static int getTypeCode() { @@ -79,12 +93,15 @@ namespace eigenpy static int registerNewType(PyTypeObject * py_type_ptr, const std::type_info * type_info_ptr, const int type_size, + const int alignment, PyArray_GetItemFunc * getitem, PyArray_SetItemFunc * setitem, PyArray_NonzeroFunc * nonzero, PyArray_CopySwapFunc * copyswap, PyArray_CopySwapNFunc * copyswapn, - PyArray_DotFunc * dotfunc); + PyArray_DotFunc * dotfunc, + PyArray_FillFunc * fill, + PyArray_FillWithScalarFunc * fillwithscalar); static Register & instance(); diff --git a/include/eigenpy/user-type.hpp b/include/eigenpy/user-type.hpp index 27ac831ee8f1b829a46ec6b1144fdb16c4c6fd20..bbd49c83586b8ed373cf17060549d95bfda60fc7 100644 --- a/include/eigenpy/user-type.hpp +++ b/include/eigenpy/user-type.hpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 INRIA +// Copyright (c) 2020-2021 INRIA // #ifndef __eigenpy_user_type_hpp__ @@ -11,8 +11,32 @@ namespace eigenpy { + /// \brief Default cast algo to cast a From to To. Can be specialized for any types. + template<typename From, typename To> + struct cast + { + static To run(const From & from) + { + return static_cast<To>(from); + } + + }; + namespace internal { + + template<typename From, typename To> + static void cast(void * from_, void * to_, npy_intp n, void * /*fromarr*/, void * /*toarr*/) + { +// std::cout << "cast::run" << std::endl; + const From* from = static_cast<From*>(from_); + To* to = static_cast<To*>(to_); + for(npy_intp i = 0; i < n; i++) + { + to[i] = eigenpy::cast<From,To>::run(from[i]); + } + } + template<typename T, int type_code = NumpyEquivalentType<T>::type_code> struct SpecialMethods { @@ -24,7 +48,21 @@ namespace eigenpy inline static npy_bool nonzero(void * /*ip*/, void * /*array*/) /*{ return (npy_bool)false; }*/; inline static void dotfunc(void * /*ip0_*/, npy_intp /*is0*/, void * /*ip1_*/, npy_intp /*is1*/, void * /*op*/, npy_intp /*n*/, void * /*arr*/); -// static void cast(void * /*from*/, void * /*to*/, npy_intp /*n*/, void * /*fromarr*/, void * /*toarr*/) {}; + inline static int fill(void* data_, npy_intp length, void* arr); + inline static int fillwithscalar(void* buffer_, npy_intp length, + void* value, void* arr); + }; + + template<typename T> + struct OffsetOf + { + struct Data + { + char c; + T v; + }; + + enum { value = offsetof(Data, v) }; }; template<typename T> @@ -47,26 +85,38 @@ namespace eigenpy std::swap(t1,t2); } } - - inline static PyObject * getitem(void * ip, void * ap) + + /// + /// \brief Get a python object from an array + /// It returns a standard Python object from + /// a single element of the array object arr pointed to by data. + /// \param[in] data Pointer to the first element of the C++ data stream + /// \param[in] arr Pointer to the first element of the Python object data stream + /// + /// \returns PyObject corresponding to the python datastream. + /// + inline static PyObject * getitem(void * ip, void * /*ap*/) { // std::cout << "getitem" << std::endl; - PyArrayObject * py_array = static_cast<PyArrayObject *>(ap); - if((py_array==NULL) || PyArray_ISBEHAVED_RO(py_array)) - { - T * elt_ptr = static_cast<T*>(ip); - bp::object m(boost::ref(*elt_ptr)); - Py_INCREF(m.ptr()); - return m.ptr(); - } - else - { - T * elt_ptr = static_cast<T*>(ip); - bp::object m(boost::ref(*elt_ptr)); - Py_INCREF(m.ptr()); - return m.ptr(); - } + T * elt_ptr = static_cast<T*>(ip); + bp::object m(*elt_ptr); + Py_INCREF(m.ptr()); + return m.ptr(); } + + /// + /// \brief Set a python object in an array. + /// It sets the Python object "item" into the array, arr, at the position + /// pointed to by data. This function deals with “misbehaved†arrays. + /// If successful, a zero is returned, otherwise, a negative one is returned + /// (and a Python error set). + + /// \param[in] src_obj Pointer to the location of the python object + /// \param[in] dest_ptr Pointer to the location in the array where the source object should be saved. + /// \param[in] array Pointer to the location of the array + /// + /// \returns int Success(0) or Failure(-1) + /// inline static int setitem(PyObject * src_obj, void * dest_ptr, void * array) { @@ -83,6 +133,10 @@ namespace eigenpy if(array_scalar_type != src_obj_type) { + std::stringstream ss; + ss << "The input type is of wrong type. "; + ss << "The expected type is " << bp::type_info(typeid(T)).name() << std::endl; + eigenpy::Exception(ss.str()); return -1; } @@ -144,28 +198,100 @@ namespace eigenpy inline static void dotfunc(void * ip0_, npy_intp is0, void * ip1_, npy_intp is1, void * op, npy_intp n, void * /*arr*/) { - T res = T(0); - char *ip0 = (char*)ip0_, *ip1 = (char*)ip1_; - npy_intp i; - for(i = 0; i < n; i++) - { - - res += *static_cast<T*>(static_cast<void*>(ip0)) - * *static_cast<T*>(static_cast<void*>(ip1)); - ip0 += is0; - ip1 += is1; - } - *static_cast<T*>(op) = res; +// std::cout << "dotfunc" << std::endl; + T res(0); + char *ip0 = (char*)ip0_, *ip1 = (char*)ip1_; + npy_intp i; + for(i = 0; i < n; i++) + { + + res += *static_cast<T*>(static_cast<void*>(ip0)) + * *static_cast<T*>(static_cast<void*>(ip1)); + ip0 += is0; + ip1 += is1; + } + *static_cast<T*>(op) = res; + } + + + inline static int fillwithscalar(void* buffer_, npy_intp length, + void* value, void* /*arr*/) + { +// std::cout << "fillwithscalar" << std::endl; + T r = *static_cast<T*>(value); + T* buffer = static_cast<T*>(buffer_); + npy_intp i; + for (i = 0; i < length; i++) { + buffer[i] = r; + } + return 0; + } + + + static int fill(void* data_, npy_intp length, void* /*arr*/) + { +// std::cout << "fillwithscalar" << std::endl; + T* data = static_cast<T*>(data_); + const T delta = data[1] - data[0]; + T r = data[1]; + npy_intp i; + for (i = 2; i < length; i++) { + r = r + delta; + data[i] = r; + } + return 0; } -// static void cast(void * from, void * to, npy_intp n, void * fromarr, void * toarr) -// { -// } - }; + }; // struct SpecialMethods<T,NPY_USERDEF> } // namespace internal + + template<typename From, typename To> + bool registerCast(const bool safe) + { + PyArray_Descr* from_array_descr = Register::getPyArrayDescr<From>(); +// int from_typenum = Register::getTypeCode<From>(); + +// PyTypeObject * to_py_type = Register::getPyType<To>(); + int to_typenum = Register::getTypeCode<To>(); + assert(to_typenum >= 0 && "to_typenum is not valid"); + assert(from_array_descr != NULL && "from_array_descr is not valid"); + + if(call_PyArray_RegisterCastFunc(from_array_descr, + to_typenum, + static_cast<PyArray_VectorUnaryFunc *>(&eigenpy::internal::cast<From,To>)) < 0) + { + std::stringstream ss; + ss + << "PyArray_RegisterCastFunc of the cast from " + << bp::type_info(typeid(From)).name() + << " to " + << bp::type_info(typeid(To)).name() + << " has failed."; + eigenpy::Exception(ss.str()); + return false; + } + + if (safe && call_PyArray_RegisterCanCast(from_array_descr, + to_typenum, + NPY_NOSCALAR) < 0) + { + std::stringstream ss; + ss + << "PyArray_RegisterCanCast of the cast from " + << bp::type_info(typeid(From)).name() + << " to " + << bp::type_info(typeid(To)).name() + << " has failed."; + eigenpy::Exception(ss.str()); + return false; + } + + return true; + } + template<typename Scalar> int registerNewType(PyTypeObject * py_type_ptr = NULL) { @@ -189,14 +315,18 @@ namespace eigenpy PyArray_CopySwapFunc * copyswap = &internal::SpecialMethods<Scalar>::copyswap; PyArray_CopySwapNFunc * copyswapn = reinterpret_cast<PyArray_CopySwapNFunc*>(&internal::SpecialMethods<Scalar>::copyswapn); PyArray_DotFunc * dotfunc = &internal::SpecialMethods<Scalar>::dotfunc; -// PyArray_CastFunc * cast = &internal::SpecialMethods<Scalar>::cast; + PyArray_FillFunc * fill = &internal::SpecialMethods<Scalar>::fill; + PyArray_FillWithScalarFunc * fillwithscalar = &internal::SpecialMethods<Scalar>::fillwithscalar; int code = Register::registerNewType(py_type_ptr, &typeid(Scalar), sizeof(Scalar), + internal::OffsetOf<Scalar>::value, getitem, setitem, nonzero, copyswap, copyswapn, - dotfunc); + dotfunc, + fill, + fillwithscalar); call_PyArray_RegisterCanCast(call_PyArray_DescrFromType(NPY_OBJECT), code, NPY_NOSCALAR); diff --git a/src/numpy.cpp b/src/numpy.cpp index fc132c9571fc550c821a01887df378d91fbf9623..5cd2ef53546fe5f99245a4f7e88dad8a70a4d941 100644 --- a/src/numpy.cpp +++ b/src/numpy.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2020 INRIA + * Copyright 2020-2021 INRIA */ #include "eigenpy/numpy.hpp" @@ -68,6 +68,11 @@ namespace eigenpy { return PyArray_RegisterCanCast(descr,totype,scalar); } - + + int call_PyArray_RegisterCastFunc(PyArray_Descr* descr, int totype, PyArray_VectorUnaryFunc* castfunc) + { + return PyArray_RegisterCastFunc(descr,totype,castfunc); + } + #endif } diff --git a/src/register.cpp b/src/register.cpp index 3fd2cccd8d8bca7c4f811e0dfb7754f43f346924..9bded2baf60470223772e1e3ba461b1b5bcc4546 100644 --- a/src/register.cpp +++ b/src/register.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2020 INRIA + * Copyright 2020-2021 INRIA */ #include "eigenpy/register.hpp" @@ -36,22 +36,43 @@ namespace eigenpy int Register::registerNewType(PyTypeObject * py_type_ptr, const std::type_info * type_info_ptr, const int type_size, + const int alignement, PyArray_GetItemFunc * getitem, PyArray_SetItemFunc * setitem, PyArray_NonzeroFunc * nonzero, PyArray_CopySwapFunc * copyswap, PyArray_CopySwapNFunc * copyswapn, - PyArray_DotFunc * dotfunc) + PyArray_DotFunc * dotfunc, + PyArray_FillFunc * fill, + PyArray_FillWithScalarFunc * fillwithscalar) { + namespace bp = boost::python; + + bp::tuple tp_bases_extended(bp::make_tuple(bp::handle<>(bp::borrowed(&PyGenericArrType_Type)))); + tp_bases_extended += bp::tuple(bp::handle<>(bp::borrowed(py_type_ptr->tp_bases))); + + Py_INCREF(tp_bases_extended.ptr()); + py_type_ptr->tp_bases = tp_bases_extended.ptr(); + + py_type_ptr->tp_flags &= ~Py_TPFLAGS_READY; // to force the rebuild +// py_type_ptr->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE; + if(PyType_Ready(py_type_ptr) < 0) // Force rebuilding of the __bases__ and mro + { + throw std::invalid_argument("PyType_Ready fails to initialize input type."); + } + PyArray_Descr * descr_ptr = new PyArray_Descr(*call_PyArray_DescrFromType(NPY_OBJECT)); PyArray_Descr & descr = *descr_ptr; descr.typeobj = py_type_ptr; descr.kind = 'V'; descr.byteorder = '='; + descr.type = 'r'; descr.elsize = type_size; - descr.flags = NPY_LIST_PICKLE | NPY_USE_GETITEM | NPY_USE_SETITEM | NPY_NEEDS_INIT | NPY_NEEDS_PYAPI; - // descr->names = PyTuple_New(0); - // descr->fields = PyDict_New(); + descr.flags = NPY_NEEDS_PYAPI | NPY_USE_GETITEM | NPY_USE_SETITEM | NPY_NEEDS_INIT; + descr.type_num = 0; + descr.names = 0; + descr.fields = 0; + descr.alignment = alignement; //call_PyArray_DescrFromType(NPY_OBJECT)->alignment; PyArray_ArrFuncs * funcs_ptr = new PyArray_ArrFuncs; PyArray_ArrFuncs & funcs = *funcs_ptr; @@ -63,11 +84,18 @@ namespace eigenpy funcs.copyswap = copyswap; funcs.copyswapn = copyswapn; funcs.dotfunc = dotfunc; + funcs.fill = fill; + funcs.fillwithscalar = fillwithscalar; // f->cast = cast; const int code = call_PyArray_RegisterDataType(descr_ptr); assert(code >= 0 && "The return code should be positive"); PyArray_Descr * new_descr = call_PyArray_DescrFromType(code); + + if(PyDict_SetItemString(py_type_ptr->tp_dict,"dtype",(PyObject*)descr_ptr) < 0) + { + throw std::invalid_argument("PyDict_SetItemString fails."); + } instance().type_to_py_type_bindings.insert(std::make_pair(type_info_ptr,py_type_ptr)); instance().py_array_descr_bindings[py_type_ptr] = new_descr; diff --git a/unittest/python/test_user_type.py b/unittest/python/test_user_type.py index 057cfe95a6896d3ffbe2b46ab68bcf6d538e5340..80d5e1a24119e040da075e3b7935b29ced4250d5 100644 --- a/unittest/python/test_user_type.py +++ b/unittest/python/test_user_type.py @@ -1,14 +1,22 @@ import user_type +import numpy as np +#from packaging import version rows = 10 cols = 20 -def test(mat): - mat.fill(mat.dtype.type(10.)) +def test(dtype): + mat = np.ones((rows,cols),dtype=dtype) mat_copy = mat.copy() assert (mat == mat_copy).all() assert not (mat != mat_copy).all() +# if version.parse(np.__version__) >= version.parse("1.21.0"): # check if it fixes for new versio of NumPy +# mat.fill(mat.dtype.type(20.)) +# mat_copy = mat.copy() +# assert((mat == mat_copy).all()) +# assert(not (mat != mat_copy).all()) + mat_op = mat + mat mat_op = mat.copy(order='F') + mat.copy(order='C') @@ -17,15 +25,32 @@ def test(mat): mat_op = mat.dot(mat.T) mat_op = mat / mat - mat_op = -mat; + mat_op = -mat assert (mat >= mat).all() assert (mat <= mat).all() assert not (mat > mat).all() assert not (mat < mat).all() -mat = user_type.create_double(rows,cols) -test(mat) +def test_cast(from_dtype,to_dtype): + np.can_cast(from_dtype,to_dtype) + + from_mat = np.zeros((rows,cols),dtype=from_dtype) + to_mat = from_mat.astype(dtype=to_dtype) + +test(user_type.CustomDouble) + +test_cast(user_type.CustomDouble,np.double) +test_cast(np.double,user_type.CustomDouble) + +test_cast(user_type.CustomDouble,np.int64) +test_cast(np.int64,user_type.CustomDouble) + +test_cast(user_type.CustomDouble,np.int32) +test_cast(np.int32,user_type.CustomDouble) + +test(user_type.CustomFloat) -mat = user_type.create_float(rows,cols) -test(mat) +v = user_type.CustomDouble(1) +a = np.array(v) +assert type(v) == a.dtype.type diff --git a/unittest/user_type.cpp b/unittest/user_type.cpp index 250f7a15759504c178d242ab9902037bd35e14e5..7cf41ab21d73f15413462af764d4d075b29173f2 100644 --- a/unittest/user_type.cpp +++ b/unittest/user_type.cpp @@ -38,7 +38,7 @@ namespace Eigen MulCost = 2 }; - static Scalar epsilon() + static CustomType<Scalar> epsilon() { return CustomType<Scalar>(std::numeric_limits<Scalar>::epsilon()); } @@ -97,6 +97,11 @@ struct CustomType CustomType operator-() const { return CustomType(-m_value); } + operator Scalar () const + { + return m_value; + } + std::string print() const { std::stringstream ss; @@ -110,7 +115,7 @@ struct CustomType return os; } -protected: +//protected: Scalar m_value; }; @@ -186,4 +191,16 @@ BOOST_PYTHON_MODULE(user_type) bp::def("build_matrix",build_matrix<double>); bp::def("print",print<double>); bp::def("print",print<float>); + + eigenpy::registerCast<DoubleType,double>(true); + eigenpy::registerCast<double,DoubleType>(true); + eigenpy::registerCast<DoubleType,int32_t>(false); + eigenpy::registerCast<int32_t,DoubleType>(true); + eigenpy::registerCast<DoubleType,int64_t>(false); + eigenpy::registerCast<int64_t,DoubleType>(true); + eigenpy::registerCast<FloatType,int64_t>(false); + eigenpy::registerCast<int64_t,FloatType>(true); + + bp::implicitly_convertible<double,DoubleType>(); + bp::implicitly_convertible<DoubleType,double>(); }