diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 29629aece4596053d0b1f763b9cb4e398f4ca890..867d72f101b99e7c3697cf2d8423f5f73e1f05ba 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -20,6 +20,8 @@ jobs: - run: | sudo apt-get update sudo apt-get install cmake libboost-all-dev libeigen3-dev python*-numpy python*-dev + echo $(sudo apt list --installed) + echo $(g++ --version) - run: cmake -DPYTHON_EXECUTABLE=$(which python${{ matrix.python }}) . - run: make -j2 - run: make test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5aef07dd643178d59e80f9bbad77b5960b16a56f..f667f1d855d6f6606e75c39d67cf6151146a4aa4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: rev: v15.0.7 hooks: - id: clang-format - args: ['--style={BasedOnStyle: Google, SortIncludes: false}'] + args: ['--style={BasedOnStyle: Google, SortIncludes: false, Standard: Cpp03}'] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/CMakeLists.txt b/CMakeLists.txt index b4b12e3773a362803c843fcfad18289be96542b6..d1187c1bfb69fb0a9a50b81dc873da948243d1f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,7 @@ string(REPLACE "-pedantic" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # If needed, fix CMake policy for APPLE systems apply_default_apple_configuration() +check_minimal_cxx_standard(11 ENFORCE) if(WIN32) set(LINK copy_if_different) @@ -164,6 +165,7 @@ set(${PROJECT_NAME}_HEADERS include/eigenpy/std-vector.hpp include/eigenpy/pickle-vector.hpp include/eigenpy/stride.hpp + include/eigenpy/tensor/eigen-from-python.hpp include/eigenpy/swig.hpp include/eigenpy/version.hpp) diff --git a/include/eigenpy/alignment.hpp b/include/eigenpy/alignment.hpp index 4bca14d36954ab744cde25a23ba4cb55f3ea95da..d37af7006bfb3124d3b6c4bfb3816ccf56b022bf 100644 --- a/include/eigenpy/alignment.hpp +++ b/include/eigenpy/alignment.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2023, INRIA + * Copyright 2023 INRIA */ #ifndef __eigenpy_alignment_hpp__ @@ -67,6 +67,23 @@ struct referent_storage< typename eigenpy::aligned_storage<referent_size<T &>::value>::type type; }; +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename Scalar, int Rank, int Options, typename IndexType> +struct referent_storage<Eigen::Tensor<Scalar, Rank, Options, IndexType> &> { + typedef Eigen::Tensor<Scalar, Rank, Options, IndexType> T; + typedef + typename eigenpy::aligned_storage<referent_size<T &>::value>::type type; +}; + +template <typename Scalar, int Rank, int Options, typename IndexType> +struct referent_storage< + const Eigen::Tensor<Scalar, Rank, Options, IndexType> &> { + typedef Eigen::Tensor<Scalar, Rank, Options, IndexType> T; + typedef + typename eigenpy::aligned_storage<referent_size<T &>::value>::type type; +}; +#endif + template <typename Scalar, int Options> struct referent_storage<Eigen::Quaternion<Scalar, Options> &> { typedef Eigen::Quaternion<Scalar, Options> T; diff --git a/include/eigenpy/details.hpp b/include/eigenpy/details.hpp index 12be4b09465632baf367c7cfb968f0ba56023540..a71c59f474a6482d6e6abc8d4434c03cd9abc7cb 100644 --- a/include/eigenpy/details.hpp +++ b/include/eigenpy/details.hpp @@ -17,24 +17,49 @@ #include "eigenpy/scalar-conversion.hpp" namespace eigenpy { -template <typename MatType, typename EigenEquivalentType> -EIGENPY_DEPRECATED void enableEigenPySpecific() { - enableEigenPySpecific<MatType>(); -} + +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct expose_eigen_type_impl; template <typename MatType> -void enableEigenPySpecific() { - if (check_registration<MatType>()) return; +struct expose_eigen_type_impl<MatType, Eigen::MatrixBase<MatType> > { + static void run() { + if (check_registration<MatType>()) return; - // to-python - EigenToPyConverter<MatType>::registration(); + // to-python + EigenToPyConverter<MatType>::registration(); #if EIGEN_VERSION_AT_LEAST(3, 2, 0) - EigenToPyConverter<Eigen::Ref<MatType> >::registration(); - EigenToPyConverter<const Eigen::Ref<const MatType> >::registration(); + EigenToPyConverter<Eigen::Ref<MatType> >::registration(); + EigenToPyConverter<const Eigen::Ref<const MatType> >::registration(); +#endif + + // from-python + EigenFromPyConverter<MatType>::registration(); + } +}; + +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType> +struct expose_eigen_type_impl<TensorType, Eigen::TensorBase<TensorType> > { + static void run() { + if (check_registration<TensorType>()) return; + + // to-python + EigenToPyConverter<TensorType>::registration(); + EigenToPyConverter<Eigen::TensorRef<TensorType> >::registration(); + EigenToPyConverter< + const Eigen::TensorRef<const TensorType> >::registration(); + + // from-python + EigenFromPyConverter<TensorType>::registration(); + } +}; #endif - // from-python - EigenFromPyConverter<MatType>::registration(); +template <typename MatType> +void enableEigenPySpecific() { + expose_eigen_type_impl<MatType>::run(); } } // namespace eigenpy diff --git a/include/eigenpy/eigen-allocator.hpp b/include/eigenpy/eigen-allocator.hpp index 589159d011344df66a5a44c7ab4ee058bc13aa8d..7c31c56f4dd8cb174010fd5dd31e43112d8c904c 100644 --- a/include/eigenpy/eigen-allocator.hpp +++ b/include/eigenpy/eigen-allocator.hpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2014-2020 CNRS INRIA +// Copyright (c) 2014-2023 CNRS INRIA // #ifndef __eigenpy_eigen_allocator_hpp__ @@ -70,31 +70,114 @@ struct init_matrix_or_array<MatType, true> { } }; +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename Tensor> +struct init_tensor { + static Tensor *run(PyArrayObject *pyArray, void *storage = NULL) { + enum { Rank = Tensor::NumDimensions }; + assert(PyArray_NDIM(pyArray) == Rank); + typedef typename Tensor::Index Index; + + Eigen::array<Index, Rank> dimensions; + for (int k = 0; k < PyArray_NDIM(pyArray); ++k) + dimensions[k] = PyArray_DIMS(pyArray)[k]; + + if (storage) + return new (storage) Tensor(dimensions); + else + return new Tensor(dimensions); + } +}; +#endif + template <typename MatType> -bool check_swap(PyArrayObject *pyArray, const Eigen::MatrixBase<MatType> &mat) { - if (PyArray_NDIM(pyArray) == 0) return false; - if (mat.rows() == PyArray_DIMS(pyArray)[0]) - return false; - else - return true; +struct check_swap_impl_matrix; + +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct check_swap_impl; + +template <typename MatType> +struct check_swap_impl<MatType, Eigen::MatrixBase<MatType> > + : check_swap_impl_matrix<MatType> {}; + +template <typename MatType> +struct check_swap_impl_matrix { + static bool run(PyArrayObject *pyArray, + const Eigen::MatrixBase<MatType> &mat) { + if (PyArray_NDIM(pyArray) == 0) return false; + if (mat.rows() == PyArray_DIMS(pyArray)[0]) + return false; + else + return true; + } +}; + +template <typename EigenType> +bool check_swap(PyArrayObject *pyArray, const EigenType &mat) { + return check_swap_impl<EigenType>::run(pyArray, mat); } +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType> +struct check_swap_impl_tensor { + static bool run(PyArrayObject * /*pyArray*/, const TensorType & /*tensor*/) { + return false; + } +}; + +template <typename TensorType> +struct check_swap_impl<TensorType, Eigen::TensorBase<TensorType> > + : check_swap_impl_tensor<TensorType> {}; +#endif + +// template <typename MatType> +// struct cast_impl_matrix; +// +// template <typename EigenType, +// typename BaseType = typename get_eigen_base_type<EigenType>::type> +// struct cast_impl; +// +// template <typename MatType> +// struct cast_impl<MatType, Eigen::MatrixBase<MatType> > +// : cast_impl_matrix<MatType> {}; +// +// template <typename MatType> +// struct cast_impl_matrix +//{ +// template <typename NewScalar, typename MatrixIn, typename MatrixOut> +// static void run(const Eigen::MatrixBase<MatrixIn> &input, +// const Eigen::MatrixBase<MatrixOut> &dest) { +// dest.const_cast_derived() = input.template cast<NewScalar>(); +// } +// }; + template <typename Scalar, typename NewScalar, + template <typename D> class EigenBase = Eigen::MatrixBase, bool cast_is_valid = FromTypeToType<Scalar, NewScalar>::value> -struct cast_matrix_or_array { +struct cast { template <typename MatrixIn, typename MatrixOut> static void run(const Eigen::MatrixBase<MatrixIn> &input, const Eigen::MatrixBase<MatrixOut> &dest) { - MatrixOut &dest_ = const_cast<MatrixOut &>(dest.derived()); - dest_ = input.template cast<NewScalar>(); + dest.const_cast_derived() = input.template cast<NewScalar>(); } }; +#ifdef EIGENPY_WITH_TENSOR_SUPPORT template <typename Scalar, typename NewScalar> -struct cast_matrix_or_array<Scalar, NewScalar, false> { +struct cast<Scalar, NewScalar, Eigen::TensorRef, true> { + template <typename TensorIn, typename TensorOut> + static void run(const TensorIn &input, TensorOut &dest) { + dest = input.template cast<NewScalar>(); + } +}; +#endif + +template <typename Scalar, typename NewScalar, + template <typename D> class EigenBase> +struct cast<Scalar, NewScalar, EigenBase, false> { template <typename MatrixIn, typename MatrixOut> - static void run(const Eigen::MatrixBase<MatrixIn> & /*input*/, - const Eigen::MatrixBase<MatrixOut> & /*dest*/) { + static void run(const MatrixIn /*input*/, const MatrixOut /*dest*/) { // do nothing assert(false && "Must never happened"); } @@ -104,19 +187,37 @@ struct cast_matrix_or_array<Scalar, NewScalar, false> { #define EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, Scalar, NewScalar, \ pyArray, mat) \ - details::cast_matrix_or_array<Scalar, NewScalar>::run( \ + details::cast<Scalar, NewScalar>::run( \ NumpyMap<MatType, Scalar>::map(pyArray, \ details::check_swap(pyArray, mat)), \ mat) #define EIGENPY_CAST_FROM_EIGEN_MATRIX_TO_PYARRAY(MatType, Scalar, NewScalar, \ mat, pyArray) \ - details::cast_matrix_or_array<Scalar, NewScalar>::run( \ + details::cast<Scalar, NewScalar>::run( \ mat, NumpyMap<MatType, NewScalar>::map( \ pyArray, details::check_swap(pyArray, mat))) +template <typename EigenType> +struct EigenAllocator; + +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct eigen_allocator_impl; + +template <typename MatType> +struct eigen_allocator_impl_matrix; + +template <typename MatType> +struct eigen_allocator_impl<MatType, Eigen::MatrixBase<MatType> > + : eigen_allocator_impl_matrix<MatType> {}; + +template <typename MatType> +struct eigen_allocator_impl<const MatType, const Eigen::MatrixBase<MatType> > + : eigen_allocator_impl_matrix<const MatType> {}; + template <typename MatType> -struct EigenAllocator { +struct eigen_allocator_impl_matrix { typedef MatType Type; typedef typename MatType::Scalar Scalar; @@ -130,8 +231,17 @@ struct EigenAllocator { Type *mat_ptr = details::init_matrix_or_array<Type>::run(pyArray, raw_ptr); Type &mat = *mat_ptr; + copy(pyArray, mat); + } + + /// \brief Copy Python array into the input matrix mat. + template <typename MatrixDerived> + static void copy(PyArrayObject *pyArray, + const Eigen::MatrixBase<MatrixDerived> &mat_) { + MatrixDerived &mat = mat_.const_cast_derived(); const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray); const int Scalar_type_code = Register::getTypeCode<Scalar>(); + if (pyArray_type_code == Scalar_type_code) { mat = NumpyMap<MatType, Scalar>::map( pyArray, details::check_swap(pyArray, mat)); // avoid useless cast @@ -185,13 +295,10 @@ struct EigenAllocator { const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray); const int Scalar_type_code = Register::getTypeCode<Scalar>(); - typedef typename NumpyMap<MatType, Scalar>::EigenMap MapType; - if (pyArray_type_code == Scalar_type_code) // no cast needed { - MapType map_pyArray = NumpyMap<MatType, Scalar>::map( - pyArray, details::check_swap(pyArray, mat)); - map_pyArray = mat; + NumpyMap<MatType, Scalar>::map(pyArray, + details::check_swap(pyArray, mat)) = mat; return; } @@ -234,6 +341,159 @@ struct EigenAllocator { } }; +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType> +struct eigen_allocator_impl_tensor; + +template <typename TensorType> +struct eigen_allocator_impl<TensorType, Eigen::TensorBase<TensorType> > + : eigen_allocator_impl_tensor<TensorType> {}; + +template <typename TensorType> +struct eigen_allocator_impl<const TensorType, + const Eigen::TensorBase<TensorType> > + : eigen_allocator_impl_tensor<const TensorType> {}; + +template <typename TensorType> +struct eigen_allocator_impl_tensor { + typedef typename TensorType::Scalar Scalar; + static void allocate( + PyArrayObject *pyArray, + boost::python::converter::rvalue_from_python_storage<TensorType> + *storage) { + void *raw_ptr = storage->storage.bytes; + assert(is_aligned(raw_ptr, EIGENPY_DEFAULT_ALIGN_BYTES) && + "The pointer is not aligned."); + + TensorType *tensor_ptr = + details::init_tensor<TensorType>::run(pyArray, raw_ptr); + TensorType &tensor = *tensor_ptr; + + copy(pyArray, tensor); + } + +#define EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, Scalar, \ + NewScalar, pyArray, tensor) \ + { \ + typename NumpyMap<TensorType, Scalar>::EigenMap pyArray_map = \ + NumpyMap<TensorType, Scalar>::map( \ + pyArray, details::check_swap(pyArray, tensor)); \ + details::cast<Scalar, NewScalar, Eigen::TensorRef>::run(pyArray_map, \ + tensor); \ + } + + /// \brief Copy Python array into the input matrix mat. + template <typename TensorDerived> + static void copy(PyArrayObject *pyArray, TensorDerived &tensor) { + const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray); + const int Scalar_type_code = Register::getTypeCode<Scalar>(); + + if (pyArray_type_code == Scalar_type_code) { + tensor = NumpyMap<TensorType, Scalar>::map( + pyArray, details::check_swap(pyArray, tensor)); // avoid useless cast + return; + } + + switch (pyArray_type_code) { + case NPY_INT: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, int, Scalar, + pyArray, tensor); + break; + case NPY_LONG: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, long, Scalar, + pyArray, tensor); + break; + case NPY_FLOAT: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, float, Scalar, + pyArray, tensor); + break; + case NPY_CFLOAT: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR( + TensorType, std::complex<float>, Scalar, pyArray, tensor); + break; + case NPY_DOUBLE: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, double, Scalar, + pyArray, tensor); + break; + case NPY_CDOUBLE: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR( + TensorType, std::complex<double>, Scalar, pyArray, tensor); + break; + case NPY_LONGDOUBLE: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR(TensorType, long double, + Scalar, pyArray, tensor); + break; + case NPY_CLONGDOUBLE: + EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_TENSOR( + TensorType, std::complex<long double>, Scalar, pyArray, tensor); + break; + default: + throw Exception("You asked for a conversion which is not implemented."); + } + } + +#define EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, \ + NewScalar, tensor, pyArray) \ + { \ + typename NumpyMap<TensorType, NewScalar>::EigenMap pyArray_map = \ + NumpyMap<TensorType, NewScalar>::map( \ + pyArray, details::check_swap(pyArray, tensor)); \ + details::cast<Scalar, NewScalar, Eigen::TensorRef>::run(tensor, \ + pyArray_map); \ + } + + /// \brief Copy mat into the Python array using Eigen::Map + static void copy(const TensorType &tensor, PyArrayObject *pyArray) { + const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray); + const int Scalar_type_code = Register::getTypeCode<Scalar>(); + + if (pyArray_type_code == Scalar_type_code) // no cast needed + { + NumpyMap<TensorType, Scalar>::map( + pyArray, details::check_swap(pyArray, tensor)) = tensor; + return; + } + + switch (pyArray_type_code) { + case NPY_INT: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, int, + tensor, pyArray); + break; + case NPY_LONG: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, long, + tensor, pyArray); + break; + case NPY_FLOAT: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, float, + tensor, pyArray); + break; + case NPY_CFLOAT: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY( + TensorType, Scalar, std::complex<float>, tensor, pyArray); + break; + case NPY_DOUBLE: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, double, + tensor, pyArray); + break; + case NPY_CDOUBLE: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY( + TensorType, Scalar, std::complex<double>, tensor, pyArray); + break; + case NPY_LONGDOUBLE: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY(TensorType, Scalar, + long double, tensor, pyArray); + break; + case NPY_CLONGDOUBLE: + EIGENPY_CAST_FROM_EIGEN_TENSOR_TO_PYARRAY( + TensorType, Scalar, std::complex<long double>, tensor, pyArray); + break; + default: + throw Exception("You asked for a conversion which is not implemented."); + } + } +}; +#endif + #if EIGEN_VERSION_AT_LEAST(3, 2, 0) /// @brief Check if we need to allocate @tparam MatType to convert @param /// pyArray. @@ -253,7 +513,7 @@ inline bool is_arr_layout_compatible_with_mat_type(PyArrayObject *pyArray) { } template <typename MatType, int Options, typename Stride> -struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > { +struct eigen_allocator_impl_matrix<Eigen::Ref<MatType, Options, Stride> > { typedef Eigen::Ref<MatType, Options, Stride> RefType; typedef typename MatType::Scalar Scalar; @@ -296,49 +556,7 @@ struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > { new (raw_ptr) StorageType(mat_ref, pyArray, mat_ptr); RefType &mat = *reinterpret_cast<RefType *>(raw_ptr); - if (pyArray_type_code == Scalar_type_code) { - mat = NumpyMap<MatType, Scalar>::map( - pyArray, details::check_swap(pyArray, mat)); // avoid useless cast - return; - } - - switch (pyArray_type_code) { - case NPY_INT: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, int, Scalar, - pyArray, mat); - break; - case NPY_LONG: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long, Scalar, - pyArray, mat); - break; - case NPY_FLOAT: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, float, Scalar, - pyArray, mat); - break; - case NPY_CFLOAT: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX( - MatType, std::complex<float>, Scalar, pyArray, mat); - break; - case NPY_DOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, double, Scalar, - pyArray, mat); - break; - case NPY_CDOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX( - MatType, std::complex<double>, Scalar, pyArray, mat); - break; - case NPY_LONGDOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long double, - Scalar, pyArray, mat); - break; - case NPY_CLONGDOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX( - MatType, std::complex<long double>, Scalar, pyArray, mat); - break; - default: - throw Exception( - "You asked for a conversion which is not implemented."); - } + EigenAllocator<MatType>::copy(pyArray, mat); } else { assert(pyArray_type_code == Scalar_type_code); typename NumpyMap<MatType, Scalar, Options, NumpyMapStride>::EigenMap @@ -355,7 +573,8 @@ struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > { }; template <typename MatType, int Options, typename Stride> -struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > { +struct eigen_allocator_impl_matrix< + const Eigen::Ref<const MatType, Options, Stride> > { typedef const Eigen::Ref<const MatType, Options, Stride> RefType; typedef typename MatType::Scalar Scalar; @@ -399,49 +618,7 @@ struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > { new (raw_ptr) StorageType(mat_ref, pyArray, mat_ptr); MatType &mat = *mat_ptr; - if (pyArray_type_code == Scalar_type_code) { - mat = NumpyMap<MatType, Scalar>::map( - pyArray, details::check_swap(pyArray, mat)); // avoid useless cast - return; - } - - switch (pyArray_type_code) { - case NPY_INT: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, int, Scalar, - pyArray, mat); - break; - case NPY_LONG: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long, Scalar, - pyArray, mat); - break; - case NPY_FLOAT: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, float, Scalar, - pyArray, mat); - break; - case NPY_CFLOAT: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX( - MatType, std::complex<float>, Scalar, pyArray, mat); - break; - case NPY_DOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, double, Scalar, - pyArray, mat); - break; - case NPY_CDOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX( - MatType, std::complex<double>, Scalar, pyArray, mat); - break; - case NPY_LONGDOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX(MatType, long double, - Scalar, pyArray, mat); - break; - case NPY_CLONGDOUBLE: - EIGENPY_CAST_FROM_PYARRAY_TO_EIGEN_MATRIX( - MatType, std::complex<long double>, Scalar, pyArray, mat); - break; - default: - throw Exception( - "You asked for a conversion which is not implemented."); - } + EigenAllocator<MatType>::copy(pyArray, mat); } else { assert(pyArray_type_code == Scalar_type_code); typename NumpyMap<MatType, Scalar, Options, NumpyMapStride>::EigenMap @@ -457,6 +634,90 @@ struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > { } }; #endif + +#ifdef EIGENPY_WITH_TENSOR_SUPPORT + +template <typename TensorType, typename TensorRef> +struct eigen_allocator_impl_tensor_ref; + +template <typename TensorType> +struct eigen_allocator_impl_tensor<Eigen::TensorRef<TensorType> > + : eigen_allocator_impl_tensor_ref<TensorType, + Eigen::TensorRef<TensorType> > {}; + +template <typename TensorType> +struct eigen_allocator_impl_tensor<const Eigen::TensorRef<const TensorType> > + : eigen_allocator_impl_tensor_ref< + const TensorType, const Eigen::TensorRef<const TensorType> > {}; + +template <typename TensorType, typename RefType> +struct eigen_allocator_impl_tensor_ref { + typedef typename TensorType::Scalar Scalar; + + typedef + typename ::boost::python::detail::referent_storage<RefType &>::StorageType + StorageType; + + static void allocate( + PyArrayObject *pyArray, + ::boost::python::converter::rvalue_from_python_storage<RefType> + *storage) { + // typedef typename StrideType< + // MatType, + // Eigen::internal::traits<RefType>::StrideType::InnerStrideAtCompileTime, + // Eigen::internal::traits<RefType>::StrideType:: + // OuterStrideAtCompileTime>::type NumpyMapStride; + + static const int Options = Eigen::internal::traits<TensorType>::Options; + + bool need_to_allocate = false; + const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray); + const int Scalar_type_code = Register::getTypeCode<Scalar>(); + if (pyArray_type_code != Scalar_type_code) need_to_allocate |= true; + // bool incompatible_layout = + // !is_arr_layout_compatible_with_mat_type<MatType>(pyArray); + // need_to_allocate |= incompatible_layout; + // if (Options != + // Eigen::Unaligned) // we need to check whether the memory is + // correctly + // // aligned and composed of a continuous segment + // { + // void *data_ptr = PyArray_DATA(pyArray); + // if (!PyArray_ISONESEGMENT(pyArray) || !is_aligned(data_ptr, + // Options)) + // need_to_allocate |= true; + // } + + void *raw_ptr = storage->storage.bytes; + if (need_to_allocate) { + typedef typename boost::remove_const<TensorType>::type TensorTypeNonConst; + TensorTypeNonConst *tensor_ptr; + tensor_ptr = details::init_tensor<TensorTypeNonConst>::run(pyArray); + RefType tensor_ref(*tensor_ptr); + + new (raw_ptr) StorageType(tensor_ref, pyArray, tensor_ptr); + + TensorTypeNonConst &tensor = *tensor_ptr; + EigenAllocator<TensorTypeNonConst>::copy(pyArray, tensor); + } else { + assert(pyArray_type_code == Scalar_type_code); + typename NumpyMap<TensorType, Scalar, Options>::EigenMap numpyMap = + NumpyMap<TensorType, Scalar, Options>::map(pyArray); + RefType tensor_ref(numpyMap); + new (raw_ptr) StorageType(tensor_ref, pyArray); + } + } + + static void copy(RefType const &ref, PyArrayObject *pyArray) { + EigenAllocator<TensorType>::copy(ref, pyArray); + } +}; + +#endif + +template <typename EigenType> +struct EigenAllocator : eigen_allocator_impl<EigenType> {}; + } // namespace eigenpy #endif // __eigenpy_eigen_allocator_hpp__ diff --git a/include/eigenpy/eigen-from-python.hpp b/include/eigenpy/eigen-from-python.hpp index f03ae268c7f4d1956d24c4cc06a01a05d6ddbf7a..f31e3512e2a00b249f2648048f82a19240cfc625 100644 --- a/include/eigenpy/eigen-from-python.hpp +++ b/include/eigenpy/eigen-from-python.hpp @@ -12,13 +12,12 @@ namespace eigenpy { -template <typename C> +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> struct expected_pytype_for_arg {}; -template <typename Scalar, int Rows, int Cols, int Options, int MaxRows, - int MaxCols> -struct expected_pytype_for_arg< - Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> > { +template <typename MatType> +struct expected_pytype_for_arg<MatType, Eigen::MatrixBase<MatType> > { static PyTypeObject const *get_pytype() { PyTypeObject const *py_type = eigenpy::getPyArrayType(); return py_type; @@ -59,44 +58,43 @@ struct copy_if_non_const<const MatType, true> { }; #if EIGEN_VERSION_AT_LEAST(3, 2, 0) -template <typename MatType, int Options, typename Stride> -struct referent_storage_eigen_ref; -template <typename MatType, int Options, typename Stride> +template <typename _RefType> struct referent_storage_eigen_ref { - typedef Eigen::Ref<MatType, Options, Stride> RefType; + typedef _RefType RefType; + typedef typename get_eigen_ref_plain_type<RefType>::type PlainObjectType; typedef typename ::eigenpy::aligned_storage< ::boost::python::detail::referent_size<RefType &>::value>::type AlignedStorage; referent_storage_eigen_ref() : pyArray(NULL), - mat_ptr(NULL), + plain_ptr(NULL), ref_ptr(reinterpret_cast<RefType *>(ref_storage.bytes)) {} referent_storage_eigen_ref(const RefType &ref, PyArrayObject *pyArray, - MatType *mat_ptr = NULL) + PlainObjectType *plain_ptr = NULL) : pyArray(pyArray), - mat_ptr(mat_ptr), + plain_ptr(plain_ptr), ref_ptr(reinterpret_cast<RefType *>(ref_storage.bytes)) { Py_INCREF(pyArray); new (ref_storage.bytes) RefType(ref); } ~referent_storage_eigen_ref() { - if (mat_ptr != NULL && PyArray_ISWRITEABLE(pyArray)) - copy_if_non_const<MatType>::run(*mat_ptr, pyArray); + if (plain_ptr != NULL && PyArray_ISWRITEABLE(pyArray)) + copy_if_non_const<PlainObjectType>::run(*plain_ptr, pyArray); Py_DECREF(pyArray); - if (mat_ptr != NULL) mat_ptr->~MatType(); + if (plain_ptr != NULL) plain_ptr->~PlainObjectType(); ref_ptr->~RefType(); } AlignedStorage ref_storage; PyArrayObject *pyArray; - MatType *mat_ptr; + PlainObjectType *plain_ptr; RefType *ref_ptr; }; #endif @@ -110,18 +108,16 @@ namespace detail { #if EIGEN_VERSION_AT_LEAST(3, 2, 0) template <typename MatType, int Options, typename Stride> struct referent_storage<Eigen::Ref<MatType, Options, Stride> &> { - typedef ::eigenpy::details::referent_storage_eigen_ref<MatType, Options, - Stride> - StorageType; + typedef Eigen::Ref<MatType, Options, Stride> RefType; + typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType; typedef typename ::eigenpy::aligned_storage< referent_size<StorageType &>::value>::type type; }; template <typename MatType, int Options, typename Stride> struct referent_storage<const Eigen::Ref<const MatType, Options, Stride> &> { - typedef ::eigenpy::details::referent_storage_eigen_ref<const MatType, Options, - Stride> - StorageType; + typedef Eigen::Ref<const MatType, Options, Stride> RefType; + typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType; typedef typename ::eigenpy::aligned_storage< referent_size<StorageType &>::value>::type type; }; @@ -173,7 +169,7 @@ struct rvalue_from_python_data<Eigen::PlainObjectBase<Derived> const &> template <typename MatType, int Options, typename Stride> struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride> &> : rvalue_from_python_storage<Eigen::Ref<MatType, Options, Stride> &> { - typedef Eigen::Ref<MatType, Options, Stride> T; + typedef Eigen::Ref<MatType, Options, Stride> RefType; #if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) && \ @@ -181,7 +177,7 @@ struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride> &> !defined(BOOST_PYTHON_SYNOPSIS) /* Synopsis' OpenCXX has trouble parsing \ this */ // This must always be a POD struct with m_data its first member. - BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<T>, + BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<RefType>, stage1) == 0); #endif @@ -199,9 +195,7 @@ struct rvalue_from_python_data<Eigen::Ref<MatType, Options, Stride> &> // Destroys any object constructed in the storage. ~rvalue_from_python_data() { - typedef ::eigenpy::details::referent_storage_eigen_ref<MatType, Options, - Stride> - StorageType; + typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType; if (this->stage1.convertible == this->storage.bytes) static_cast<StorageType *>((void *)this->storage.bytes)->~StorageType(); } @@ -212,7 +206,7 @@ struct rvalue_from_python_data< const Eigen::Ref<const MatType, Options, Stride> &> : rvalue_from_python_storage< const Eigen::Ref<const MatType, Options, Stride> &> { - typedef const Eigen::Ref<const MatType, Options, Stride> T; + typedef Eigen::Ref<const MatType, Options, Stride> RefType; #if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) && \ @@ -220,7 +214,7 @@ struct rvalue_from_python_data< !defined(BOOST_PYTHON_SYNOPSIS) /* Synopsis' OpenCXX has trouble parsing \ this */ // This must always be a POD struct with m_data its first member. - BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<T>, + BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage<RefType>, stage1) == 0); #endif @@ -238,9 +232,7 @@ struct rvalue_from_python_data< // Destroys any object constructed in the storage. ~rvalue_from_python_data() { - typedef ::eigenpy::details::referent_storage_eigen_ref<const MatType, - Options, Stride> - StorageType; + typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType; if (this->stage1.convertible == this->storage.bytes) static_cast<StorageType *>((void *)this->storage.bytes)->~StorageType(); } @@ -269,8 +261,23 @@ void eigen_from_py_construct( memory->convertible = storage->storage.bytes; } -template <typename MatType, typename _Scalar> -struct EigenFromPy { +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct eigen_from_py_impl { + typedef typename EigenType::Scalar Scalar; + + /// \brief Determine if pyObj can be converted into a MatType object + static void *convertible(PyObject *pyObj); + + /// \brief Allocate memory and copy pyObj in the new storage + static void construct(PyObject *pyObj, + bp::converter::rvalue_from_python_stage1_data *memory); + + static void registration(); +}; + +template <typename MatType> +struct eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> > { typedef typename MatType::Scalar Scalar; /// \brief Determine if pyObj can be converted into a MatType object @@ -283,8 +290,20 @@ struct EigenFromPy { static void registration(); }; -template <typename MatType, typename _Scalar> -void *EigenFromPy<MatType, _Scalar>::convertible(PyObject *pyObj) { +#ifdef EIGENPY_MSVC_COMPILER +template <typename EigenType> +struct EigenFromPy<EigenType, + typename boost::remove_reference<EigenType>::type::Scalar> +#else +template <typename EigenType, typename _Scalar> +struct EigenFromPy +#endif + : eigen_from_py_impl<EigenType> { +}; + +template <typename MatType> +void *eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> >::convertible( + PyObject *pyObj) { if (!call_PyArray_Check(reinterpret_cast<PyObject *>(pyObj))) return 0; PyArrayObject *pyArray = reinterpret_cast<PyArrayObject *>(pyObj); @@ -384,26 +403,33 @@ void *EigenFromPy<MatType, _Scalar>::convertible(PyObject *pyObj) { return pyArray; } -template <typename MatType, typename _Scalar> -void EigenFromPy<MatType, _Scalar>::construct( +template <typename MatType> +void eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> >::construct( PyObject *pyObj, bp::converter::rvalue_from_python_stage1_data *memory) { eigen_from_py_construct<MatType>(pyObj, memory); } -template <typename MatType, typename _Scalar> -void EigenFromPy<MatType, _Scalar>::registration() { +template <typename MatType> +void eigen_from_py_impl<MatType, Eigen::MatrixBase<MatType> >::registration() { bp::converter::registry::push_back( - reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible), - &EigenFromPy::construct, bp::type_id<MatType>() + reinterpret_cast<void *(*)(_object *)>(&eigen_from_py_impl::convertible), + &eigen_from_py_impl::construct, bp::type_id<MatType>() #ifndef BOOST_PYTHON_NO_PY_SIGNATURES - , + , &eigenpy::expected_pytype_for_arg<MatType>::get_pytype #endif ); } +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct eigen_from_py_converter_impl; + +template <typename EigenType> +struct EigenFromPyConverter : eigen_from_py_converter_impl<EigenType> {}; + template <typename MatType> -struct EigenFromPyConverter { +struct eigen_from_py_converter_impl<MatType, Eigen::MatrixBase<MatType> > { static void registration() { EigenFromPy<MatType>::registration(); @@ -535,4 +561,8 @@ struct EigenFromPy<const Eigen::Ref<const MatType, Options, Stride> > { } // namespace eigenpy +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +#include "eigenpy/tensor/eigen-from-python.hpp" +#endif + #endif // __eigenpy_eigen_from_python_hpp__ diff --git a/include/eigenpy/eigen-to-python.hpp b/include/eigenpy/eigen-to-python.hpp index 96f2afdec77d5de88a302af5505e81c71f436843..d7084ddf232cc70a494264672af4a3fc66aef8f1 100644 --- a/include/eigenpy/eigen-to-python.hpp +++ b/include/eigenpy/eigen-to-python.hpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2014-2020 CNRS INRIA +// Copyright (c) 2014-2023 CNRS INRIA // #ifndef __eigenpy_eigen_to_python_hpp__ @@ -12,6 +12,7 @@ #include "eigenpy/eigen-allocator.hpp" #include "eigenpy/numpy-allocator.hpp" #include "eigenpy/numpy-type.hpp" +#include "eigenpy/registration.hpp" namespace boost { namespace python { @@ -60,8 +61,33 @@ struct to_python_indirect< namespace eigenpy { -template <typename MatType, typename _Scalar> -struct EigenToPy { +EIGENPY_DOCUMENTATION_START_IGNORE + +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct eigen_to_py_impl; + +template <typename MatType> +struct eigen_to_py_impl_matrix; + +template <typename MatType> +struct eigen_to_py_impl<MatType, Eigen::MatrixBase<MatType> > + : eigen_to_py_impl_matrix<MatType> {}; + +template <typename MatType> +struct eigen_to_py_impl<MatType&, Eigen::MatrixBase<MatType> > + : eigen_to_py_impl_matrix<MatType&> {}; + +template <typename MatType> +struct eigen_to_py_impl<const MatType, const Eigen::MatrixBase<MatType> > + : eigen_to_py_impl_matrix<const MatType> {}; + +template <typename MatType> +struct eigen_to_py_impl<const MatType&, const Eigen::MatrixBase<MatType> > + : eigen_to_py_impl_matrix<const MatType&> {}; + +template <typename MatType> +struct eigen_to_py_impl_matrix { static PyObject* convert( typename boost::add_reference< typename boost::add_const<MatType>::type>::type mat) { @@ -91,39 +117,53 @@ struct EigenToPy { // Create an instance (either np.array or np.matrix) return NumpyType::make(pyArray).ptr(); } - - static PyTypeObject const* get_pytype() { return getPyArrayType(); } }; -template <typename MatType, int Options, typename Stride, typename _Scalar> -struct EigenToPy<Eigen::Ref<MatType, Options, Stride>, _Scalar> { - static PyObject* convert(const Eigen::Ref<MatType, Options, Stride>& mat) { - typedef Eigen::Ref<MatType, Options, Stride> EigenRef; +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType> +struct eigen_to_py_impl_tensor; - assert((mat.rows() < INT_MAX) && (mat.cols() < INT_MAX) && - "Matrix range larger than int ... should never happen."); - const npy_intp R = (npy_intp)mat.rows(), C = (npy_intp)mat.cols(); +template <typename TensorType> +struct eigen_to_py_impl<TensorType, Eigen::TensorBase<TensorType> > + : eigen_to_py_impl_tensor<TensorType> {}; - PyArrayObject* pyArray; - // Allocate Python memory - if ((((!(C == 1) != !(R == 1)) && !MatType::IsVectorAtCompileTime) || - MatType::IsVectorAtCompileTime) && - NumpyType::getType() == - ARRAY_TYPE) // Handle array with a single dimension - { - npy_intp shape[1] = {C == 1 ? R : C}; - pyArray = NumpyAllocator<EigenRef>::allocate(const_cast<EigenRef&>(mat), - 1, shape); - } else { - npy_intp shape[2] = {R, C}; - pyArray = NumpyAllocator<EigenRef>::allocate(const_cast<EigenRef&>(mat), - 2, shape); - } +template <typename TensorType> +struct eigen_to_py_impl<const TensorType, const Eigen::TensorBase<TensorType> > + : eigen_to_py_impl_tensor<const TensorType> {}; + +template <typename TensorType> +struct eigen_to_py_impl_tensor { + static PyObject* convert( + typename boost::add_reference< + typename boost::add_const<TensorType>::type>::type tensor) { + // typedef typename boost::remove_const< + // typename boost::remove_reference<Tensor>::type>::type + // TensorDerived; + + static const int NumIndices = TensorType::NumIndices; + npy_intp shape[NumIndices]; + for (int k = 0; k < NumIndices; ++k) shape[k] = tensor.dimension(k); + + PyArrayObject* pyArray = NumpyAllocator<TensorType>::allocate( + const_cast<TensorType&>(tensor), NumIndices, shape); // Create an instance (either np.array or np.matrix) return NumpyType::make(pyArray).ptr(); } +}; +#endif +EIGENPY_DOCUMENTATION_END_IGNORE + +#ifdef EIGENPY_MSVC_COMPILER +template <typename EigenType> +struct EigenToPy<EigenType, + typename boost::remove_reference<EigenType>::type::Scalar> +#else +template <typename EigenType, typename _Scalar> +struct EigenToPy +#endif + : eigen_to_py_impl<EigenType> { static PyTypeObject const* get_pytype() { return getPyArrayType(); } }; diff --git a/include/eigenpy/eigen-typedef.hpp b/include/eigenpy/eigen-typedef.hpp index b6310e36e8550bb0564abe361a27199239c73b2e..d195d11a688a6187e1ef3776f3c2b2993275cabb 100644 --- a/include/eigenpy/eigen-typedef.hpp +++ b/include/eigenpy/eigen-typedef.hpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 INRIA +// Copyright (c) 2020-2023 INRIA // #ifndef __eigenpy_eigen_typedef_hpp__ @@ -31,6 +31,7 @@ EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, Eigen::Dynamic, X) \ EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 2) \ EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 3) \ - EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 4) + EIGENPY_MAKE_FIXED_TYPEDEFS(Type, Options, TypeSuffix, 4) \ + EIGENPY_MAKE_TYPEDEFS(Type, Options, TypeSuffix, 1, 1) #endif // ifndef __eigenpy_eigen_typedef_hpp__ diff --git a/include/eigenpy/eigenpy.hpp b/include/eigenpy/eigenpy.hpp index f9e5de95231ca98775e32c4ef17b20a07434f575..bdbca6f4c2ce15699652650a8966fd81a551bab7 100644 --- a/include/eigenpy/eigenpy.hpp +++ b/include/eigenpy/eigenpy.hpp @@ -1,13 +1,12 @@ /* * Copyright 2014-2019, CNRS - * Copyright 2018-2020, INRIA + * Copyright 2018-2023, INRIA */ #ifndef __eigenpy_eigenpy_hpp__ #define __eigenpy_eigenpy_hpp__ #include "eigenpy/fwd.hpp" -#include "eigenpy/deprecated.hpp" #include "eigenpy/eigen-typedef.hpp" #include "eigenpy/expose.hpp" @@ -20,22 +19,18 @@ namespace eigenpy { */ void EIGENPY_DLLAPI enableEigenPy(); -/* Enable the Eigen--Numpy serialization for the templated MatrixBase class. - * The second template argument is used for inheritance of Eigen classes. If - * using a native Eigen::MatrixBase, simply repeat the same arg twice. */ +/* Enable the Eigen--Numpy serialization for the templated MatType class.*/ template <typename MatType> void enableEigenPySpecific(); -/* Enable the Eigen--Numpy serialization for the templated MatrixBase class. - * The second template argument is used for inheritance of Eigen classes. If - * using a native Eigen::MatrixBase, simply repeat the same arg twice. */ -template <typename MatType, typename EigenEquivalentType> -EIGENPY_DEPRECATED void enableEigenPySpecific(); - template <typename Scalar, int Options> EIGEN_DONT_INLINE void exposeType() { EIGENPY_MAKE_TYPEDEFS_ALL_SIZES(Scalar, Options, s); + EIGENPY_UNUSED_TYPE(Vector1s); + EIGENPY_UNUSED_TYPE(RowVector1s); + ENABLE_SPECIFIC_MATRIX_TYPE(Matrix1s); + ENABLE_SPECIFIC_MATRIX_TYPE(Vector2s); ENABLE_SPECIFIC_MATRIX_TYPE(RowVector2s); ENABLE_SPECIFIC_MATRIX_TYPE(Matrix2s); @@ -62,6 +57,12 @@ EIGEN_DONT_INLINE void exposeType() { template <typename Scalar> EIGEN_DONT_INLINE void exposeType() { exposeType<Scalar, 0>(); + +#ifdef EIGENPY_WITH_TENSOR_SUPPORT + enableEigenPySpecific<Eigen::Tensor<Scalar, 1> >(); + enableEigenPySpecific<Eigen::Tensor<Scalar, 2> >(); + enableEigenPySpecific<Eigen::Tensor<Scalar, 3> >(); +#endif } } // namespace eigenpy diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp index 3c30d6639e3828b85379d46c5e738fb41242f9b5..c44d103334b1da23a294cd30aa95333fd083e32b 100644 --- a/include/eigenpy/fwd.hpp +++ b/include/eigenpy/fwd.hpp @@ -61,6 +61,9 @@ EIGENPY_PRAGMA_WARNING( \ EIGENPY_STRINGCAT("this file is deprecated: ", the_message)) +#define EIGENPY_DOCUMENTATION_START_IGNORE /// \cond +#define EIGENPY_DOCUMENTATION_END_IGNORE /// \endcond + #include "eigenpy/config.hpp" // Silence a warning about a deprecated use of boost bind by boost python @@ -85,6 +88,11 @@ namespace bp = boost::python; #include <Eigen/Core> #include <Eigen/Geometry> +#ifdef EIGENPY_WITH_CXX11_SUPPORT +#include <unsupported/Eigen/CXX11/Tensor> +#define EIGENPY_WITH_TENSOR_SUPPORT +#endif + #if EIGEN_VERSION_AT_LEAST(3, 2, 90) #define EIGENPY_DEFAULT_ALIGNMENT_VALUE Eigen::Aligned16 #else @@ -96,6 +104,7 @@ namespace bp = boost::python; #define EIGENPY_NO_ALIGNMENT_VALUE Eigen::Unaligned #define EIGENPY_UNUSED_VARIABLE(var) (void)(var) +#define EIGENPY_UNUSED_TYPE(type) EIGENPY_UNUSED_VARIABLE((type *)(NULL)) #ifdef EIGENPY_WITH_CXX11_SUPPORT #include <memory> @@ -114,6 +123,50 @@ template <typename MatType, typename Scalar = typename boost::remove_reference<MatType>::type::Scalar> struct EigenFromPy; + +template <typename T> +struct remove_const_reference { + typedef typename boost::remove_const< + typename boost::remove_reference<T>::type>::type type; +}; + +template <typename EigenType> +struct get_eigen_base_type { + typedef typename remove_const_reference<EigenType>::type EigenType_; + typedef typename boost::mpl::if_< + boost::is_base_of<Eigen::MatrixBase<EigenType_>, EigenType_>, + Eigen::MatrixBase<EigenType_> +#ifdef EIGENPY_WITH_TENSOR_SUPPORT + , + typename boost::mpl::if_< + boost::is_base_of<Eigen::TensorBase<EigenType_>, EigenType_>, + Eigen::TensorBase<EigenType_>, void>::type +#else + , + void +#endif + >::type _type; + + typedef typename boost::mpl::if_< + boost::is_const<typename boost::remove_reference<EigenType>::type>, + const _type, _type>::type type; +}; + +template <typename EigenType> +struct get_eigen_ref_plain_type; + +template <typename MatType, int Options, typename Stride> +struct get_eigen_ref_plain_type<Eigen::Ref<MatType, Options, Stride> > { + typedef typename Eigen::internal::traits< + Eigen::Ref<MatType, Options, Stride> >::PlainObjectType type; +}; + +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType> +struct get_eigen_ref_plain_type<Eigen::TensorRef<TensorType> > { + typedef TensorType type; +}; +#endif } // namespace eigenpy #include "eigenpy/alignment.hpp" diff --git a/include/eigenpy/numpy-allocator.hpp b/include/eigenpy/numpy-allocator.hpp index 30c287366623bf6d4336844dc00423d953743ce1..61653940ac6104a02bda828eeaef530d32574c49 100644 --- a/include/eigenpy/numpy-allocator.hpp +++ b/include/eigenpy/numpy-allocator.hpp @@ -1,19 +1,49 @@ /* - * Copyright 2020-2022 INRIA + * Copyright 2020-2023 INRIA */ #ifndef __eigenpy_numpy_allocator_hpp__ #define __eigenpy_numpy_allocator_hpp__ -#include "eigenpy/eigen-allocator.hpp" #include "eigenpy/fwd.hpp" +#include "eigenpy/eigen-allocator.hpp" #include "eigenpy/numpy-type.hpp" #include "eigenpy/register.hpp" namespace eigenpy { +template <typename EigenType, typename BaseType> +struct numpy_allocator_impl; + +template <typename EigenType> +struct numpy_allocator_impl_matrix; + +template <typename MatType> +struct numpy_allocator_impl< + MatType, Eigen::MatrixBase<typename remove_const_reference<MatType>::type> > + : numpy_allocator_impl_matrix<MatType> {}; + +template <typename MatType> +struct numpy_allocator_impl< + const MatType, + const Eigen::MatrixBase<typename remove_const_reference<MatType>::type> > + : numpy_allocator_impl_matrix<const MatType> {}; + +// template <typename MatType> +// struct numpy_allocator_impl<MatType &, Eigen::MatrixBase<MatType> > : +// numpy_allocator_impl_matrix<MatType &> +//{}; + +template <typename MatType> +struct numpy_allocator_impl<const MatType &, const Eigen::MatrixBase<MatType> > + : numpy_allocator_impl_matrix<const MatType &> {}; + +template <typename EigenType, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct NumpyAllocator : numpy_allocator_impl<EigenType, BaseType> {}; + template <typename MatType> -struct NumpyAllocator { +struct numpy_allocator_impl_matrix { template <typename SimilarMatrixType> static PyArrayObject *allocate( const Eigen::MatrixBase<SimilarMatrixType> &mat, npy_intp nd, @@ -31,8 +61,40 @@ struct NumpyAllocator { } }; +#ifdef EIGENPY_WITH_TENSOR_SUPPORT + +template <typename TensorType> +struct numpy_allocator_impl_tensor; + +template <typename TensorType> +struct numpy_allocator_impl<TensorType, Eigen::TensorBase<TensorType> > + : numpy_allocator_impl_tensor<TensorType> {}; + +template <typename TensorType> +struct numpy_allocator_impl<const TensorType, + const Eigen::TensorBase<TensorType> > + : numpy_allocator_impl_tensor<const TensorType> {}; + +template <typename TensorType> +struct numpy_allocator_impl_tensor { + template <typename TensorDerived> + static PyArrayObject *allocate(const TensorDerived &tensor, npy_intp nd, + npy_intp *shape) { + const int code = Register::getTypeCode<typename TensorDerived::Scalar>(); + PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_SimpleNew( + static_cast<int>(nd), shape, code); + + // Copy data + EigenAllocator<TensorDerived>::copy( + static_cast<const TensorDerived &>(tensor), pyArray); + + return pyArray; + } +}; +#endif + template <typename MatType> -struct NumpyAllocator<MatType &> { +struct numpy_allocator_impl_matrix<MatType &> { template <typename SimilarMatrixType> static PyArrayObject *allocate(Eigen::PlainObjectBase<SimilarMatrixType> &mat, npy_intp nd, npy_intp *shape) { @@ -58,7 +120,7 @@ struct NumpyAllocator<MatType &> { #if EIGEN_VERSION_AT_LEAST(3, 2, 0) template <typename MatType, int Options, typename Stride> -struct NumpyAllocator<Eigen::Ref<MatType, Options, Stride> > { +struct numpy_allocator_impl_matrix<Eigen::Ref<MatType, Options, Stride> > { typedef Eigen::Ref<MatType, Options, Stride> RefType; static PyArrayObject *allocate(RefType &mat, npy_intp nd, npy_intp *shape) { @@ -93,7 +155,7 @@ struct NumpyAllocator<Eigen::Ref<MatType, Options, Stride> > { #endif template <typename MatType> -struct NumpyAllocator<const MatType &> { +struct numpy_allocator_impl_matrix<const MatType &> { template <typename SimilarMatrixType> static PyArrayObject *allocate( const Eigen::PlainObjectBase<SimilarMatrixType> &mat, npy_intp nd, @@ -122,7 +184,8 @@ struct NumpyAllocator<const MatType &> { #if EIGEN_VERSION_AT_LEAST(3, 2, 0) template <typename MatType, int Options, typename Stride> -struct NumpyAllocator<const Eigen::Ref<const MatType, Options, Stride> > { +struct numpy_allocator_impl_matrix< + const Eigen::Ref<const MatType, Options, Stride> > { typedef const Eigen::Ref<const MatType, Options, Stride> RefType; static PyArrayObject *allocate(RefType &mat, npy_intp nd, npy_intp *shape) { @@ -156,6 +219,70 @@ struct NumpyAllocator<const Eigen::Ref<const MatType, Options, Stride> > { } }; +#endif + +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType> +struct numpy_allocator_impl_tensor<Eigen::TensorRef<TensorType> > { + typedef Eigen::TensorRef<TensorType> RefType; + + static PyArrayObject *allocate(RefType &tensor, npy_intp nd, + npy_intp *shape) { + typedef typename RefType::Scalar Scalar; + static const bool IsRowMajor = TensorType::Options & Eigen::RowMajorBit; + enum { + NPY_ARRAY_MEMORY_CONTIGUOUS = + IsRowMajor ? NPY_ARRAY_CARRAY : NPY_ARRAY_FARRAY + }; + + if (NumpyType::sharedMemory()) { + const int Scalar_type_code = Register::getTypeCode<Scalar>(); + // static const Index NumIndices = TensorType::NumIndices; + + // const int elsize = + // call_PyArray_DescrFromType(Scalar_type_code)->elsize; npy_intp + // strides[NumIndices]; + + PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New( + getPyArrayType(), static_cast<int>(nd), shape, Scalar_type_code, NULL, + const_cast<Scalar *>(tensor.data()), + NPY_ARRAY_MEMORY_CONTIGUOUS | NPY_ARRAY_ALIGNED); + + return pyArray; + } else { + return NumpyAllocator<TensorType>::allocate(tensor, nd, shape); + } + } +}; + +template <typename TensorType> +struct numpy_allocator_impl_tensor<const Eigen::TensorRef<const TensorType> > { + typedef const Eigen::TensorRef<const TensorType> RefType; + + static PyArrayObject *allocate(RefType &tensor, npy_intp nd, + npy_intp *shape) { + typedef typename RefType::Scalar Scalar; + static const bool IsRowMajor = TensorType::Options & Eigen::RowMajorBit; + enum { + NPY_ARRAY_MEMORY_CONTIGUOUS_RO = + IsRowMajor ? NPY_ARRAY_CARRAY_RO : NPY_ARRAY_FARRAY_RO + }; + + if (NumpyType::sharedMemory()) { + const int Scalar_type_code = Register::getTypeCode<Scalar>(); + + PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New( + getPyArrayType(), static_cast<int>(nd), shape, Scalar_type_code, NULL, + const_cast<Scalar *>(tensor.data()), + NPY_ARRAY_MEMORY_CONTIGUOUS_RO | NPY_ARRAY_ALIGNED); + + return pyArray; + } else { + return NumpyAllocator<TensorType>::allocate(tensor, nd, shape); + } + } +}; + #endif } // namespace eigenpy diff --git a/include/eigenpy/numpy-map.hpp b/include/eigenpy/numpy-map.hpp index ae6b1e6931f8828ecaaf646b912f0e486c5d89c1..e73f1f42d5f71569dd6403833cf4ea6f2426784b 100644 --- a/include/eigenpy/numpy-map.hpp +++ b/include/eigenpy/numpy-map.hpp @@ -1,6 +1,6 @@ /* * Copyright 2014-2019, CNRS - * Copyright 2018-2020, INRIA + * Copyright 2018-2023, INRIA */ #ifndef __eigenpy_numpy_map_hpp__ @@ -11,42 +11,40 @@ #include "eigenpy/stride.hpp" namespace eigenpy { + template <typename MatType, typename InputScalar, int AlignmentValue, typename Stride, bool IsVector = MatType::IsVectorAtCompileTime> -struct NumpyMapTraits {}; +struct numpy_map_impl_matrix; -/* Wrap a numpy::array with an Eigen::Map. No memory copy. */ -template <typename MatType, typename InputScalar, - int AlignmentValue = EIGENPY_NO_ALIGNMENT_VALUE, - typename Stride = typename StrideType<MatType>::type> -struct NumpyMap { - typedef NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride> Impl; - typedef typename Impl::EigenMap EigenMap; +template <typename EigenType, typename InputScalar, int AlignmentValue, + typename Stride, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct numpy_map_impl; - static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false); -}; - -} // namespace eigenpy +template <typename MatType, typename InputScalar, int AlignmentValue, + typename Stride> +struct numpy_map_impl<MatType, InputScalar, AlignmentValue, Stride, + Eigen::MatrixBase<MatType> > + : numpy_map_impl_matrix<MatType, InputScalar, AlignmentValue, Stride> {}; -/* --- DETAILS - * ------------------------------------------------------------------ */ -/* --- DETAILS - * ------------------------------------------------------------------ */ -/* --- DETAILS - * ------------------------------------------------------------------ */ +template <typename MatType, typename InputScalar, int AlignmentValue, + typename Stride> +struct numpy_map_impl<const MatType, InputScalar, AlignmentValue, Stride, + const Eigen::MatrixBase<MatType> > + : numpy_map_impl_matrix<const MatType, InputScalar, AlignmentValue, + Stride> {}; -namespace eigenpy { template <typename MatType, typename InputScalar, int AlignmentValue, typename Stride> -struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, false> { +struct numpy_map_impl_matrix<MatType, InputScalar, AlignmentValue, Stride, + false> { typedef Eigen::Matrix<InputScalar, MatType::RowsAtCompileTime, MatType::ColsAtCompileTime, MatType::Options> EquivalentInputMatrixType; typedef Eigen::Map<EquivalentInputMatrixType, AlignmentValue, Stride> EigenMap; - static EigenMap mapImpl(PyArrayObject* pyArray, - bool swap_dimensions = false) { + static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false) { enum { OuterStrideAtCompileTime = Stride::OuterStrideAtCompileTime, InnerStrideAtCompileTime = Stride::InnerStrideAtCompileTime, @@ -135,15 +133,15 @@ struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, false> { template <typename MatType, typename InputScalar, int AlignmentValue, typename Stride> -struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, true> { +struct numpy_map_impl_matrix<MatType, InputScalar, AlignmentValue, Stride, + true> { typedef Eigen::Matrix<InputScalar, MatType::RowsAtCompileTime, MatType::ColsAtCompileTime, MatType::Options> EquivalentInputMatrixType; typedef Eigen::Map<EquivalentInputMatrixType, AlignmentValue, Stride> EigenMap; - static EigenMap mapImpl(PyArrayObject* pyArray, - bool swap_dimensions = false) { + static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false) { EIGENPY_UNUSED_VARIABLE(swap_dimensions); assert(PyArray_NDIM(pyArray) <= 2); @@ -176,13 +174,58 @@ struct NumpyMapTraits<MatType, InputScalar, AlignmentValue, Stride, true> { } }; -template <typename MatType, typename InputScalar, int AlignmentValue, +#ifdef EIGENPY_WITH_TENSOR_SUPPORT + +template <typename TensorType, typename InputScalar, int AlignmentValue, + typename Stride> +struct numpy_map_impl_tensor; + +template <typename TensorType, typename InputScalar, int AlignmentValue, + typename Stride> +struct numpy_map_impl<TensorType, InputScalar, AlignmentValue, Stride, + Eigen::TensorBase<TensorType> > + : numpy_map_impl_tensor<TensorType, InputScalar, AlignmentValue, Stride> {}; + +template <typename TensorType, typename InputScalar, int AlignmentValue, typename Stride> -typename NumpyMap<MatType, InputScalar, AlignmentValue, Stride>::EigenMap -NumpyMap<MatType, InputScalar, AlignmentValue, Stride>::map( - PyArrayObject* pyArray, bool swap_dimensions) { - return Impl::mapImpl(pyArray, swap_dimensions); -} +struct numpy_map_impl<const TensorType, InputScalar, AlignmentValue, Stride, + const Eigen::TensorBase<TensorType> > + : numpy_map_impl_tensor<const TensorType, InputScalar, AlignmentValue, + Stride> {}; + +template <typename TensorType, typename InputScalar, int AlignmentValue, + typename Stride> +struct numpy_map_impl_tensor { + typedef TensorType Tensor; + typedef typename Eigen::internal::traits<TensorType>::Index Index; + static const int Options = Eigen::internal::traits<TensorType>::Options; + static const int NumIndices = TensorType::NumIndices; + + typedef Eigen::Tensor<InputScalar, NumIndices, Options, Index> + EquivalentInputTensorType; + typedef typename EquivalentInputTensorType::Dimensions Dimensions; + typedef Eigen::TensorMap<EquivalentInputTensorType, Options> EigenMap; + + static EigenMap map(PyArrayObject* pyArray, bool swap_dimensions = false) { + EIGENPY_UNUSED_VARIABLE(swap_dimensions); + assert(PyArray_NDIM(pyArray) == NumIndices || NumIndices == Eigen::Dynamic); + + Eigen::DSizes<Index, NumIndices> dimensions; + for (int k = 0; k < PyArray_NDIM(pyArray); ++k) + dimensions[k] = PyArray_DIMS(pyArray)[k]; + + InputScalar* pyData = reinterpret_cast<InputScalar*>(PyArray_DATA(pyArray)); + return EigenMap(pyData, dimensions); + } +}; +#endif + +/* Wrap a numpy::array with an Eigen::Map. No memory copy. */ +template <typename EigenType, typename InputScalar, + int AlignmentValue = EIGENPY_NO_ALIGNMENT_VALUE, + typename Stride = typename StrideType<EigenType>::type> +struct NumpyMap + : numpy_map_impl<EigenType, InputScalar, AlignmentValue, Stride> {}; } // namespace eigenpy diff --git a/include/eigenpy/stride.hpp b/include/eigenpy/stride.hpp index 10cd223e02721832c5464f732da063fcc9e3d178..41680986545d217f905956e628ac0a96e2fd7031 100644 --- a/include/eigenpy/stride.hpp +++ b/include/eigenpy/stride.hpp @@ -1,26 +1,65 @@ /* * Copyright 2014-2019, CNRS - * Copyright 2018-2019, INRIA + * Copyright 2018-2023, INRIA */ #ifndef __eigenpy_stride_hpp__ #define __eigenpy_stride_hpp__ -#include <Eigen/Core> +#include <eigenpy/fwd.hpp> namespace eigenpy { -template <typename MatType, int InnerStride = Eigen::Dynamic, - int OuterStride = Eigen::Dynamic, + +template <typename MatType, int InnerStride, int OuterStride, bool IsVectorAtCompileTime = MatType::IsVectorAtCompileTime> -struct StrideType { +struct stride_type_matrix { typedef Eigen::Stride<OuterStride, InnerStride> type; }; template <typename MatType, int InnerStride, int OuterStride> -struct StrideType<MatType, InnerStride, OuterStride, true> { +struct stride_type_matrix<MatType, InnerStride, OuterStride, true> { typedef Eigen::InnerStride<InnerStride> type; }; +template <typename EigenType, int InnerStride, int OuterStride, + typename BaseType = typename get_eigen_base_type<EigenType>::type> +struct stride_type; + +template <typename MatrixType, int InnerStride, int OuterStride> +struct stride_type<MatrixType, InnerStride, OuterStride, + Eigen::MatrixBase<MatrixType> > { + typedef + typename stride_type_matrix<MatrixType, InnerStride, OuterStride>::type + type; +}; + +template <typename MatrixType, int InnerStride, int OuterStride> +struct stride_type<const MatrixType, InnerStride, OuterStride, + const Eigen::MatrixBase<MatrixType> > { + typedef typename stride_type_matrix<const MatrixType, InnerStride, + OuterStride>::type type; +}; + +#ifdef EIGENPY_WITH_TENSOR_SUPPORT +template <typename TensorType, int InnerStride, int OuterStride> +struct stride_type<TensorType, InnerStride, OuterStride, + Eigen::TensorBase<TensorType> > { + typedef Eigen::Stride<OuterStride, InnerStride> type; +}; + +template <typename TensorType, int InnerStride, int OuterStride> +struct stride_type<const TensorType, InnerStride, OuterStride, + const Eigen::TensorBase<TensorType> > { + typedef Eigen::Stride<OuterStride, InnerStride> type; +}; +#endif + +template <typename EigenType, int InnerStride = Eigen::Dynamic, + int OuterStride = Eigen::Dynamic> +struct StrideType { + typedef typename stride_type<EigenType, InnerStride, OuterStride>::type type; +}; + } // namespace eigenpy #endif // ifndef __eigenpy_stride_hpp__ diff --git a/include/eigenpy/tensor/eigen-from-python.hpp b/include/eigenpy/tensor/eigen-from-python.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ed4fed0e9bf94643ba17041ae28ff0ff3d43e783 --- /dev/null +++ b/include/eigenpy/tensor/eigen-from-python.hpp @@ -0,0 +1,226 @@ +// +// Copyright (c) 2023 INRIA +// + +#ifndef __eigenpy_tensor_eigen_from_python_hpp__ +#define __eigenpy_tensor_eigen_from_python_hpp__ + +#include "eigenpy/fwd.hpp" +#include "eigenpy/eigen-allocator.hpp" +#include "eigenpy/numpy-type.hpp" +#include "eigenpy/scalar-conversion.hpp" + +namespace eigenpy { + +template <typename TensorType> +struct expected_pytype_for_arg<TensorType, Eigen::TensorBase<TensorType> > { + static PyTypeObject const *get_pytype() { + PyTypeObject const *py_type = eigenpy::getPyArrayType(); + return py_type; + } +}; + +} // namespace eigenpy + +namespace boost { +namespace python { +namespace converter { + +template <typename Scalar, int Rank, int Options, typename IndexType> +struct expected_pytype_for_arg<Eigen::Tensor<Scalar, Rank, Options, IndexType> > + : eigenpy::expected_pytype_for_arg< + Eigen::Tensor<Scalar, Rank, Options, IndexType> > {}; + +template <typename Scalar, int Rank, int Options, typename IndexType> +struct rvalue_from_python_data< + Eigen::Tensor<Scalar, Rank, Options, IndexType> const &> + : ::eigenpy::rvalue_from_python_data< + Eigen::Tensor<Scalar, Rank, Options, IndexType> const &> { + typedef Eigen::Tensor<Scalar, Rank, Options, IndexType> T; + EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(T const &) +}; + +template <typename Derived> +struct rvalue_from_python_data<Eigen::TensorBase<Derived> const &> + : ::eigenpy::rvalue_from_python_data<Derived const &> { + EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(Derived const &) +}; + +} // namespace converter +} // namespace python +} // namespace boost + +namespace boost { +namespace python { +namespace detail { +template <typename TensorType> +struct referent_storage<Eigen::TensorRef<TensorType> &> { + typedef Eigen::TensorRef<TensorType> RefType; + typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType; + typedef typename ::eigenpy::aligned_storage< + referent_size<StorageType &>::value>::type type; +}; + +template <typename TensorType> +struct referent_storage<const Eigen::TensorRef<const TensorType> &> { + typedef Eigen::TensorRef<const TensorType> RefType; + typedef ::eigenpy::details::referent_storage_eigen_ref<RefType> StorageType; + typedef typename ::eigenpy::aligned_storage< + referent_size<StorageType &>::value>::type type; +}; +} // namespace detail +} // namespace python +} // namespace boost + +namespace eigenpy { + +template <typename TensorType> +struct eigen_from_py_impl<TensorType, Eigen::TensorBase<TensorType> > { + typedef typename TensorType::Scalar Scalar; + + /// \brief Determine if pyObj can be converted into a MatType object + static void *convertible(PyObject *pyObj); + + /// \brief Allocate memory and copy pyObj in the new storage + static void construct(PyObject *pyObj, + bp::converter::rvalue_from_python_stage1_data *memory); + + static void registration(); +}; + +template <typename TensorType> +void * +eigen_from_py_impl<TensorType, Eigen::TensorBase<TensorType> >::convertible( + PyObject *pyObj) { + if (!call_PyArray_Check(reinterpret_cast<PyObject *>(pyObj))) return 0; + + typedef typename Eigen::internal::traits<TensorType>::Index Index; + static const Index NumIndices = TensorType::NumIndices; + + PyArrayObject *pyArray = reinterpret_cast<PyArrayObject *>(pyObj); + + if (!np_type_is_convertible_into_scalar<Scalar>( + EIGENPY_GET_PY_ARRAY_TYPE(pyArray))) + return 0; + + if (!(PyArray_NDIM(pyArray) == NumIndices || NumIndices == Eigen::Dynamic)) + return 0; + +#ifdef NPY_1_8_API_VERSION + if (!(PyArray_FLAGS(pyArray))) +#else + if (!(PyArray_FLAGS(pyArray) & NPY_ALIGNED)) +#endif + { + return 0; + } + + return pyArray; +} + +template <typename TensorType> +void eigen_from_py_impl<TensorType, Eigen::TensorBase<TensorType> >::construct( + PyObject *pyObj, bp::converter::rvalue_from_python_stage1_data *memory) { + eigen_from_py_construct<TensorType>(pyObj, memory); +} + +template <typename TensorType> +void eigen_from_py_impl<TensorType, + Eigen::TensorBase<TensorType> >::registration() { + bp::converter::registry::push_back( + reinterpret_cast<void *(*)(_object *)>(&eigen_from_py_impl::convertible), + &eigen_from_py_impl::construct, bp::type_id<TensorType>() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , + &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype +#endif + ); +} + +template <typename TensorType> +struct eigen_from_py_converter_impl<TensorType, + Eigen::TensorBase<TensorType> > { + static void registration() { + EigenFromPy<TensorType>::registration(); + + // Add conversion to Eigen::TensorBase<TensorType> + typedef Eigen::TensorBase<TensorType> TensorBase; + EigenFromPy<TensorBase>::registration(); + + // Add conversion to Eigen::TensorRef<TensorType> + typedef Eigen::TensorRef<TensorType> RefType; + EigenFromPy<RefType>::registration(); + + // Add conversion to Eigen::TensorRef<const TensorType> + typedef const Eigen::TensorRef<const TensorType> ConstRefType; + EigenFromPy<ConstRefType>::registration(); + } +}; + +template <typename TensorType> +struct EigenFromPy<Eigen::TensorBase<TensorType> > : EigenFromPy<TensorType> { + typedef EigenFromPy<TensorType> EigenFromPyDerived; + typedef Eigen::TensorBase<TensorType> Base; + + static void registration() { + bp::converter::registry::push_back( + reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible), + &EigenFromPy::construct, bp::type_id<Base>() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , + &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype +#endif + ); + } +}; + +template <typename TensorType> +struct EigenFromPy<Eigen::TensorRef<TensorType> > { + typedef Eigen::TensorRef<TensorType> RefType; + typedef typename TensorType::Scalar Scalar; + + /// \brief Determine if pyObj can be converted into a MatType object + static void *convertible(PyObject *pyObj) { + if (!call_PyArray_Check(pyObj)) return 0; + PyArrayObject *pyArray = reinterpret_cast<PyArrayObject *>(pyObj); + if (!PyArray_ISWRITEABLE(pyArray)) return 0; + return EigenFromPy<TensorType>::convertible(pyObj); + } + + static void registration() { + bp::converter::registry::push_back( + reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible), + &eigen_from_py_construct<RefType>, bp::type_id<RefType>() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , + &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype +#endif + ); + } +}; + +template <typename TensorType> +struct EigenFromPy<const Eigen::TensorRef<const TensorType> > { + typedef const Eigen::TensorRef<const TensorType> ConstRefType; + typedef typename TensorType::Scalar Scalar; + + /// \brief Determine if pyObj can be converted into a MatType object + static void *convertible(PyObject *pyObj) { + return EigenFromPy<TensorType>::convertible(pyObj); + } + + static void registration() { + bp::converter::registry::push_back( + reinterpret_cast<void *(*)(_object *)>(&EigenFromPy::convertible), + &eigen_from_py_construct<ConstRefType>, bp::type_id<ConstRefType>() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , + &eigenpy::expected_pytype_for_arg<TensorType>::get_pytype +#endif + ); + } +}; + +} // namespace eigenpy + +#endif // __eigenpy_tensor_eigen_from_python_hpp__ diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 8e0f194a8953d33e4bde8a6815675551564ee6cd..667c41929f77cf9115682d532feb54479bf3f98e 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2014-2019 CNRS Copyright (c) 2018-2021 INRIA +# Copyright (c) 2014-2019 CNRS Copyright (c) 2018-2023 INRIA # macro(ADD_LIB_UNIT_TEST test) @@ -25,6 +25,7 @@ macro(ADD_LIB_UNIT_TEST test) endmacro(ADD_LIB_UNIT_TEST) add_lib_unit_test(matrix) +add_lib_unit_test(tensor) add_lib_unit_test(geometry) add_lib_unit_test(complex) add_lib_unit_test(return_by_ref) @@ -39,6 +40,8 @@ endif() add_lib_unit_test(std_vector) add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest") + +add_python_unit_test("py-tensor" "unittest/python/test_tensor.py" "unittest") add_python_unit_test("py-geometry" "unittest/python/test_geometry.py" "unittest") add_python_unit_test("py-complex" "unittest/python/test_complex.py" "unittest") diff --git a/unittest/matrix.cpp b/unittest/matrix.cpp index 95f97da556c41ade052a664f7552799a0425972e..e202c572b878aca945885092100de509e225f1dc 100644 --- a/unittest/matrix.cpp +++ b/unittest/matrix.cpp @@ -19,6 +19,11 @@ Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> matrix1x1( return ReturnType::Constant(1, 1, value); } +template <typename Scalar> +void matrix1x1_input(const Eigen::Matrix<Scalar, 1, 1>& mat) { + std::cout << mat << std::endl; +} + Eigen::VectorXd emptyVector() { Eigen::VectorXd vec; vec.resize(0); @@ -146,6 +151,8 @@ BOOST_PYTHON_MODULE(matrix) { bp::def("vector1x1", vector1x1<double>); bp::def("matrix1x1", matrix1x1<double>); + bp::def("matrix1x1", matrix1x1_input<double>); + bp::def("matrix1x1_int", matrix1x1_input<int>); bp::def("naturals", naturalsXX); bp::def("naturalsX", naturalsX); diff --git a/unittest/python/test_matrix.py b/unittest/python/test_matrix.py index 0edf1889683ea57f828bcd44ca668f266ed4b91d..af384c837148759ef9ccbdd020ecc3e4c90e7ba6 100644 --- a/unittest/python/test_matrix.py +++ b/unittest/python/test_matrix.py @@ -15,6 +15,10 @@ if verbose: v = eigenpy.emptyVector() assert v.shape == (0,) +if verbose: + print("===> From Py to Matrix1") +eigenpy.matrix1x1(np.array([1])) + if verbose: print("===> From MatrixXd to Py") M = eigenpy.naturals(3, 3, verbose) diff --git a/unittest/python/test_tensor.py b/unittest/python/test_tensor.py new file mode 100644 index 0000000000000000000000000000000000000000..b82d34f91fb57877e628cce4842ebbbd31312955 --- /dev/null +++ b/unittest/python/test_tensor.py @@ -0,0 +1,32 @@ +from __future__ import print_function + +import numpy as np +import tensor + +dim = np.array([10, 20, 30], dtype=np.int32) +t = tensor.TensorContainer3(dim) +r = t.get_ref() +r[:] = 0.0 +c = t.get_copy() +r2 = tensor.ref(r) +cr = tensor.const_ref(r) +c2 = tensor.copy(cr) + +assert np.all(c == r) +assert np.all(r2 == r) +assert np.all(cr == r) +assert np.all(c2 == r) + +tensor.print_base(cr) +tensor.print_ref(cr) +tensor.print(cr) + +r2[:] = 100.0 +assert not np.all(c == r) +assert not np.all(c2 == r) +assert np.all(r2 == r) +assert np.all(cr == r) + +tensor.print_base(cr) +tensor.print_ref(cr) +tensor.print(cr) diff --git a/unittest/tensor.cpp b/unittest/tensor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..80838ebfc06d0a810d4d252f3c9322220a4850bb --- /dev/null +++ b/unittest/tensor.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2023 INRIA + */ + +#include <iostream> + +#include "eigenpy/eigenpy.hpp" +namespace bp = boost::python; + +template <typename Scalar> +Eigen::Matrix<Scalar, Eigen::Dynamic, 1> vector1x1(const Scalar& value) { + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> ReturnType; + return ReturnType::Constant(1, value); +} + +template <typename Scalar> +Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> matrix1x1( + const Scalar& value) { + typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> ReturnType; + return ReturnType::Constant(1, 1, value); +} + +template <typename Tensor> +Eigen::TensorRef<Tensor> make_ref(Tensor& tensor) { + return Eigen::TensorRef<Tensor>(tensor); +} + +template <typename Tensor> +void fill(Eigen::TensorRef<Tensor> tensor, typename Tensor::Scalar value) { + for (Eigen::DenseIndex k = 0; k < tensor.size(); ++k) + tensor.coeffRef(k) = value; +} + +template <typename Tensor> +void print(const Tensor& tensor) { + std::cout << tensor << std::endl; +} + +template <typename Tensor> +void print_ref(const Eigen::TensorRef<const Tensor> tensor) { + print(tensor); +} + +template <typename Tensor> +void print_base(const Eigen::TensorBase<Tensor>& tensor) { + print(tensor); +} + +template <typename Tensor> +Tensor copy(const Eigen::TensorBase<Tensor>& tensor) { + return const_cast<Tensor&>(static_cast<const Tensor&>(tensor)); +} + +template <typename Tensor> +Eigen::TensorRef<Tensor> ref(Eigen::TensorRef<Tensor> tensor) { + return tensor; +} + +template <typename Tensor> +const Eigen::TensorRef<const Tensor> const_ref( + const Eigen::TensorRef<const Tensor> tensor) { + return tensor; +} + +template <typename Scalar, int Rank> +Eigen::Tensor<Scalar, Rank> emptyTensor() { + return Eigen::Tensor<Scalar, Rank>(); +} + +template <typename Scalar> +Eigen::Tensor<Scalar, 1> zeroTensor1(const Eigen::DenseIndex r) { + Eigen::Tensor<Scalar, 1> tensor(r); + tensor.setZero(); + return tensor; +} + +template <typename Scalar> +Eigen::Tensor<Scalar, 2> zeroTensor2(const Eigen::DenseIndex r, + const Eigen::DenseIndex s) { + Eigen::Tensor<Scalar, 2> tensor(r, s); + tensor.setZero(); + return tensor; +} + +template <typename Scalar> +Eigen::Tensor<Scalar, 3> zeroTensor3(const Eigen::DenseIndex r, + const Eigen::DenseIndex s, + const Eigen::DenseIndex t) { + Eigen::Tensor<Scalar, 3> tensor(r, s, t); + tensor.setZero(); + return tensor; +} + +template <typename Scalar> +Eigen::Tensor<Scalar, 1> createTensor1(const Eigen::DenseIndex r, + Scalar value) { + Eigen::Tensor<Scalar, 1> tensor(r); + fill(make_ref(tensor), value); + return tensor; +} + +template <typename Scalar> +Eigen::Tensor<Scalar, 2> createTensor2(const Eigen::DenseIndex r, + const Eigen::DenseIndex s, + Scalar value) { + Eigen::Tensor<Scalar, 2> tensor(r, s); + fill(make_ref(tensor), value); + return tensor; +} + +template <typename Scalar> +Eigen::Tensor<Scalar, 3> createTensor3(const Eigen::DenseIndex r, + const Eigen::DenseIndex s, + const Eigen::DenseIndex t, + Scalar value) { + Eigen::Tensor<Scalar, 3> tensor(r, s, t); + fill(make_ref(tensor), value); + return tensor; +} + +template <typename Scalar, int Rank> +struct TensorContainer { + typedef Eigen::Tensor<Scalar, Rank> Tensor; + typedef Eigen::TensorRef<Tensor> TensorRef; + typedef Eigen::Matrix<Scalar, Rank, 1> Dimensions; + + Tensor m_tensor; + TensorContainer(const Dimensions& dims) { + typedef Eigen::array<typename Tensor::Index, Rank> InternalDimension; + InternalDimension _dims; + for (size_t k = 0; k < Rank; ++k) _dims[k] = dims[Eigen::DenseIndex(k)]; + + m_tensor = Tensor(_dims); + } + + Tensor get_copy() const { return m_tensor; } + TensorRef get_ref() { return TensorRef(m_tensor); } +}; + +template <typename Scalar, int Rank> +void exposeTensorContainer() { + typedef TensorContainer<Scalar, Rank> T; + const std::string class_name = "TensorContainer" + std::to_string(Rank); + bp::class_<T>(class_name.c_str(), bp::no_init) + .def(bp::init<typename T::Dimensions>()) + .def("get_copy", &T::get_copy) + .def("get_ref", &T::get_ref, + bp::with_custodian_and_ward_postcall<0, 1>()); +} + +BOOST_PYTHON_MODULE(tensor) { + using namespace Eigen; + eigenpy::enableEigenPy(); + + typedef Eigen::Tensor<double, 1> Tensor1; + typedef Eigen::Tensor<double, 2> Tensor2; + typedef Eigen::Tensor<double, 3> Tensor3; + + bp::def("emptyTensor1", emptyTensor<double, 1>); + bp::def("emptyTensor2", emptyTensor<double, 2>); + bp::def("emptyTensor3", emptyTensor<double, 3>); + + bp::def("zeroTensor1", zeroTensor1<double>); + bp::def("zeroTensor2", zeroTensor2<double>); + bp::def("zeroTensor3", zeroTensor3<double>); + + bp::def("createTensor1", createTensor1<double>); + bp::def("createTensor2", createTensor2<double>); + bp::def("createTensor3", createTensor3<double>); + + bp::def("print", print<Tensor1>); + bp::def("print", print<Tensor2>); + bp::def("print", print<Tensor3>); + + bp::def("print_ref", print_ref<Tensor1>); + bp::def("print_ref", print_ref<Tensor2>); + bp::def("print_ref", print_ref<Tensor3>); + + bp::def("print_base", print_base<Tensor1>); + bp::def("print_base", print_base<Tensor2>); + bp::def("print_base", print_base<Tensor3>); + + bp::def("copy", copy<Tensor1>); + bp::def("copy", copy<Tensor2>); + bp::def("copy", copy<Tensor3>); + + bp::def("fill", fill<Tensor1>); + bp::def("fill", fill<Tensor2>); + bp::def("fill", fill<Tensor3>); + + bp::def("ref", ref<Tensor1>, bp::with_custodian_and_ward_postcall<0, 1>()); + bp::def("ref", ref<Tensor2>, bp::with_custodian_and_ward_postcall<0, 1>()); + bp::def("ref", ref<Tensor3>, bp::with_custodian_and_ward_postcall<0, 1>()); + + bp::def("const_ref", const_ref<Tensor1>, + bp::with_custodian_and_ward_postcall<0, 1>()); + bp::def("const_ref", const_ref<Tensor2>, + bp::with_custodian_and_ward_postcall<0, 1>()); + bp::def("const_ref", const_ref<Tensor3>, + bp::with_custodian_and_ward_postcall<0, 1>()); + + exposeTensorContainer<double, 1>(); + exposeTensorContainer<double, 2>(); + exposeTensorContainer<double, 3>(); +}