diff --git a/.github/workflows/conda/environment_macos_linux.yml b/.github/workflows/conda/environment_macos_linux.yml index 34842449f..5d8e21c5e 100644 --- a/.github/workflows/conda/environment_macos_linux.yml +++ b/.github/workflows/conda/environment_macos_linux.yml @@ -10,3 +10,4 @@ dependencies: - ccache - cxx-compiler - ninja + - scipy diff --git a/.github/workflows/conda/environment_windows.yml b/.github/workflows/conda/environment_windows.yml index beb2be2af..af5e1c07a 100644 --- a/.github/workflows/conda/environment_windows.yml +++ b/.github/workflows/conda/environment_windows.yml @@ -9,3 +9,4 @@ dependencies: - boost - ccache - ninja + - scipy diff --git a/.github/workflows/jrl-cmakemodules.yml b/.github/workflows/jrl-cmakemodules.yml index 9207b4b30..0de0d5ec9 100644 --- a/.github/workflows/jrl-cmakemodules.yml +++ b/.github/workflows/jrl-cmakemodules.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - run: sudo apt install libboost-all-dev libeigen3-dev python3-numpy + - run: sudo apt install libboost-all-dev libeigen3-dev python3-numpy python3-scipy - run: cmake . @@ -21,6 +21,6 @@ jobs: with: submodules: false path: eigenpy - - run: sudo apt install libboost-all-dev libeigen3-dev python3-numpy + - run: sudo apt install libboost-all-dev libeigen3-dev python3-numpy python3-scipy - run: cmake -B build -S eigenpy - run: grep -qvz CMAKE_PROJECT_VERSION:STATIC=0.0 build/CMakeCache.txt diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 94d5cd074..c3c182b81 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,10 +16,10 @@ jobs: submodules: 'true' - run: | sudo apt-get update - sudo apt-get install cmake libboost-all-dev libeigen3-dev python*-numpy python*-dev + sudo apt-get install cmake libboost-all-dev libeigen3-dev python*-numpy python*-dev python*-scipy echo $(sudo apt list --installed) echo $(g++ --version) - - run: cmake -DPYTHON_EXECUTABLE=$(which python${{ matrix.python }}) . + - run: cmake . -DPYTHON_EXECUTABLE=$(which python${{ matrix.python }}) -DBUILD_TESTING_SCIPY=ON - run: make -j2 - run: make test diff --git a/.github/workflows/macos-linux-conda.yml b/.github/workflows/macos-linux-conda.yml index 150f10f18..f40513a54 100644 --- a/.github/workflows/macos-linux-conda.yml +++ b/.github/workflows/macos-linux-conda.yml @@ -88,6 +88,7 @@ jobs: -G "Ninja" \ -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX \ -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING_SCIPY=ON \ -DPYTHON_EXECUTABLE=$(which python3) - name: Uninstall EigenPy diff --git a/.github/workflows/reloc.yml b/.github/workflows/reloc.yml index fd5cab798..ae2712029 100644 --- a/.github/workflows/reloc.yml +++ b/.github/workflows/reloc.yml @@ -30,7 +30,7 @@ jobs: run: git -C /RELOC/SRC clone --recursive $(pwd) - name: install dependencies - run: sudo apt install libboost-all-dev libeigen3-dev python-is-python3 python3-numpy python3-pip ccache + run: sudo apt install libboost-all-dev libeigen3-dev python-is-python3 python3-numpy python3-pip python3-scipy ccache - name: update CMake run: pip install -U pip && pip install -U cmake diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f5eb9c7..422e29f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Support for `Eigen::SparseMatrix` types ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426)) + +### Fixed +- Fix the issue of missing exposition of Eigen types with __int64 scalar type ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426)) + ## [3.3.0] - 2024-01-23 ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index a280328c6..1c9024257 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,15 +41,8 @@ set(CMAKE_VERBOSE_MAKEFILE True) # ---------------------------------------------------- option(INSTALL_DOCUMENTATION "Generate and install the documentation" OFF) option(SUFFIX_SO_VERSION "Suffix library name with its version" OFF) - -if(DEFINED BUILD_UNIT_TESTS) - message( - AUTHOR_WARNING - "BUILD_UNIT_TESTS is deprecated. Use BUILD_TESTING instead.\ - If you are manually building EigenPy from source in an existing build folder,\ - we suggest that you delete your build folder and make a new one.") - set(BUILD_TESTING ${BUILD_UNIT_TESTS}) -endif(DEFINED BUILD_UNIT_TESTS) +option(BUILD_TESTING_SCIPY + "Build the SciPy tests (scipy should be installed on the machine)" OFF) include("${JRL_CMAKE_MODULES}/base.cmake") compute_project_args(PROJECT_ARGS LANGUAGES CXX) @@ -170,6 +163,9 @@ set(${PROJECT_NAME}_HEADERS include/eigenpy/pickle-vector.hpp include/eigenpy/stride.hpp include/eigenpy/tensor/eigen-from-python.hpp + include/eigenpy/sparse/eigen-from-python.hpp + include/eigenpy/scipy-allocator.hpp + include/eigenpy/scipy-type.hpp include/eigenpy/swig.hpp include/eigenpy/version.hpp) @@ -209,6 +205,7 @@ set(${PROJECT_NAME}_SOURCES src/angle-axis.cpp src/quaternion.cpp src/geometry-conversion.cpp + src/scipy-type.cpp src/std-vector.cpp src/optional.cpp src/version.cpp) diff --git a/cmake b/cmake index 24abb7409..935e2154e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 24abb7409c89f3c7fb08e55cf3edc823781ff9fb +Subproject commit 935e2154e02e84f147046278bfd64e27a5f0850a diff --git a/include/eigenpy/details.hpp b/include/eigenpy/details.hpp index e1bab2ade..538812abe 100644 --- a/include/eigenpy/details.hpp +++ b/include/eigenpy/details.hpp @@ -1,6 +1,6 @@ /* * Copyright 2014-2019, CNRS - * Copyright 2018-2023, INRIA + * Copyright 2018-2024, INRIA */ #ifndef __eigenpy_details_hpp__ @@ -40,6 +40,24 @@ struct expose_eigen_type_impl, Scalar> { } }; +template +struct expose_eigen_type_impl, + Scalar> { + static void run() { + if (check_registration()) return; + + // to-python + EigenToPyConverter::registration(); + // #if EIGEN_VERSION_AT_LEAST(3, 2, 0) + // EigenToPyConverter >::registration(); + // EigenToPyConverter >::registration(); + // #endif + + // from-python + EigenFromPyConverter::registration(); + } +}; + #ifdef EIGENPY_WITH_TENSOR_SUPPORT template struct expose_eigen_type_impl, diff --git a/include/eigenpy/eigen-from-python.hpp b/include/eigenpy/eigen-from-python.hpp index f31e3512e..904c2dd9c 100644 --- a/include/eigenpy/eigen-from-python.hpp +++ b/include/eigenpy/eigen-from-python.hpp @@ -62,7 +62,7 @@ struct copy_if_non_const { template struct referent_storage_eigen_ref { typedef _RefType RefType; - typedef typename get_eigen_ref_plain_type::type PlainObjectType; + typedef typename get_eigen_plain_type::type PlainObjectType; typedef typename ::eigenpy::aligned_storage< ::boost::python::detail::referent_size::value>::type AlignedStorage; @@ -565,4 +565,6 @@ struct EigenFromPy > { #include "eigenpy/tensor/eigen-from-python.hpp" #endif +#include "eigenpy/sparse/eigen-from-python.hpp" + #endif // __eigenpy_eigen_from_python_hpp__ diff --git a/include/eigenpy/eigen-to-python.hpp b/include/eigenpy/eigen-to-python.hpp index 2b5e039bc..e6c4c1408 100644 --- a/include/eigenpy/eigen-to-python.hpp +++ b/include/eigenpy/eigen-to-python.hpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2014-2023 CNRS INRIA +// Copyright (c) 2014-2024 CNRS INRIA // #ifndef __eigenpy_eigen_to_python_hpp__ @@ -11,7 +11,9 @@ #include "eigenpy/eigen-allocator.hpp" #include "eigenpy/numpy-allocator.hpp" +#include "eigenpy/scipy-allocator.hpp" #include "eigenpy/numpy-type.hpp" +#include "eigenpy/scipy-type.hpp" #include "eigenpy/registration.hpp" namespace boost { @@ -116,6 +118,50 @@ struct eigen_to_py_impl_matrix { // Create an instance (either np.array or np.matrix) return NumpyType::make(pyArray).ptr(); } + + static PyTypeObject const* get_pytype() { return getPyArrayType(); } +}; + +template +struct eigen_to_py_impl_sparse_matrix; + +template +struct eigen_to_py_impl > + : eigen_to_py_impl_sparse_matrix {}; + +template +struct eigen_to_py_impl > + : eigen_to_py_impl_sparse_matrix {}; + +template +struct eigen_to_py_impl > + : eigen_to_py_impl_sparse_matrix {}; + +template +struct eigen_to_py_impl > + : eigen_to_py_impl_sparse_matrix {}; + +template +struct eigen_to_py_impl_sparse_matrix { + enum { IsRowMajor = MatType::IsRowMajor }; + + static PyObject* convert( + typename boost::add_reference< + typename boost::add_const::type>::type mat) { + typedef typename boost::remove_const< + typename boost::remove_reference::type>::type MatrixDerived; + + // Allocate and perform the copy + PyObject* pyArray = + ScipyAllocator::allocate(const_cast(mat)); + + return pyArray; + } + + static PyTypeObject const* get_pytype() { + return IsRowMajor ? ScipyType::getScipyCSRMatrixType() + : ScipyType::getScipyCSCMatrixType(); + } }; #ifdef EIGENPY_WITH_TENSOR_SUPPORT @@ -149,6 +195,8 @@ struct eigen_to_py_impl_tensor { // Create an instance (either np.array or np.matrix) return NumpyType::make(pyArray).ptr(); } + + static PyTypeObject const* get_pytype() { return getPyArrayType(); } }; #endif @@ -163,7 +211,6 @@ template struct EigenToPy #endif : eigen_to_py_impl { - static PyTypeObject const* get_pytype() { return getPyArrayType(); } }; template diff --git a/include/eigenpy/eigenpy.hpp b/include/eigenpy/eigenpy.hpp index 8a6cecc28..f4edf090d 100644 --- a/include/eigenpy/eigenpy.hpp +++ b/include/eigenpy/eigenpy.hpp @@ -1,6 +1,6 @@ /* * Copyright 2014-2019, CNRS - * Copyright 2018-2023, INRIA + * Copyright 2018-2024, INRIA */ #ifndef __eigenpy_eigenpy_hpp__ @@ -54,6 +54,8 @@ EIGEN_DONT_INLINE void exposeType() { ENABLE_SPECIFIC_MATRIX_TYPE(VectorXs); ENABLE_SPECIFIC_MATRIX_TYPE(RowVectorXs); ENABLE_SPECIFIC_MATRIX_TYPE(MatrixXs); + + enableEigenPySpecific >(); } template diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp index aada9ac1c..7f5294995 100644 --- a/include/eigenpy/fwd.hpp +++ b/include/eigenpy/fwd.hpp @@ -89,6 +89,7 @@ namespace bp = boost::python; #undef BOOST_BIND_GLOBAL_PLACEHOLDERS #include +#include #include #ifdef EIGENPY_WITH_CXX11_SUPPORT @@ -108,6 +109,12 @@ namespace bp = boost::python; #define EIGENPY_UNUSED_VARIABLE(var) (void)(var) #define EIGENPY_UNUSED_TYPE(type) EIGENPY_UNUSED_VARIABLE((type *)(NULL)) +#ifndef NDEBUG +#define EIGENPY_USED_VARIABLE_ONLY_IN_DEBUG_MODE(var) +#else +#define EIGENPY_USED_VARIABLE_ONLY_IN_DEBUG_MODE(var) \ + EIGENPY_UNUSED_VARIABLE(var) +#endif #ifdef EIGENPY_WITH_CXX11_SUPPORT #include @@ -138,17 +145,20 @@ struct get_eigen_base_type { typedef typename remove_const_reference::type EigenType_; typedef typename boost::mpl::if_< boost::is_base_of, EigenType_>, - Eigen::MatrixBase -#ifdef EIGENPY_WITH_TENSOR_SUPPORT - , + Eigen::MatrixBase, typename boost::mpl::if_< - boost::is_base_of, EigenType_>, - Eigen::TensorBase, void>::type + boost::is_base_of, EigenType_>, + Eigen::SparseMatrixBase +#ifdef EIGENPY_WITH_TENSOR_SUPPORT + , + typename boost::mpl::if_< + boost::is_base_of, EigenType_>, + Eigen::TensorBase, void>::type #else - , - void + , + void #endif - >::type _type; + >::type>::type _type; typedef typename boost::mpl::if_< boost::is_const::type>, @@ -156,17 +166,17 @@ struct get_eigen_base_type { }; template -struct get_eigen_ref_plain_type; +struct get_eigen_plain_type; template -struct get_eigen_ref_plain_type > { +struct get_eigen_plain_type > { typedef typename Eigen::internal::traits< Eigen::Ref >::PlainObjectType type; }; #ifdef EIGENPY_WITH_TENSOR_SUPPORT template -struct get_eigen_ref_plain_type > { +struct get_eigen_plain_type > { typedef TensorType type; }; #endif diff --git a/include/eigenpy/numpy-type.hpp b/include/eigenpy/numpy-type.hpp index 816390b47..25dee99f2 100644 --- a/include/eigenpy/numpy-type.hpp +++ b/include/eigenpy/numpy-type.hpp @@ -24,10 +24,19 @@ bool np_type_is_convertible_into_scalar(const int np_type) { if (NumpyEquivalentType::type_code == np_type) return true; switch (np_type) { +#ifdef WIN32 + case NPY_INT: + case NPY_LONG: + return FromTypeToType::value; + case NPY_LONGLONG: + return FromTypeToType<__int64, Scalar>::value; +#else case NPY_INT: return FromTypeToType::value; case NPY_LONG: + case NPY_LONGLONG: return FromTypeToType::value; +#endif case NPY_FLOAT: return FromTypeToType::value; case NPY_CFLOAT: @@ -56,8 +65,6 @@ struct EIGENPY_DLLAPI NumpyType { static bool sharedMemory(); - static bp::object getNumpyType(); - static const PyTypeObject* getNumpyArrayType(); protected: diff --git a/include/eigenpy/register.hpp b/include/eigenpy/register.hpp index 64d09d76e..ba3bb0674 100644 --- a/include/eigenpy/register.hpp +++ b/include/eigenpy/register.hpp @@ -20,6 +20,25 @@ namespace eigenpy { struct EIGENPY_DLLAPI Register { static PyArray_Descr *getPyArrayDescr(PyTypeObject *py_type_ptr); + static PyArray_Descr *getPyArrayDescrFromTypeNum(const int type_num); + + template + static PyArray_Descr *getPyArrayDescrFromScalarType() { + if (!isNumpyNativeType()) { + const std::type_info &info = typeid(Scalar); + if (instance().type_to_py_type_bindings.find(&info) != + instance().type_to_py_type_bindings.end()) { + PyTypeObject *py_type = instance().type_to_py_type_bindings[&info]; + return instance().py_array_descr_bindings[py_type]; + } else + return nullptr; + } else { + PyArray_Descr *new_descr = + call_PyArray_DescrFromType(NumpyEquivalentType::type_code); + return new_descr; + } + } + template static bool isRegistered() { return isRegistered(Register::getPyType()); diff --git a/include/eigenpy/scalar-conversion.hpp b/include/eigenpy/scalar-conversion.hpp index 1756b12a5..8a8532d9a 100644 --- a/include/eigenpy/scalar-conversion.hpp +++ b/include/eigenpy/scalar-conversion.hpp @@ -1,70 +1,18 @@ // -// Copyright (c) 2014-2020 CNRS INRIA +// Copyright (c) 2014-2024 CNRS INRIA // #ifndef __eigenpy_scalar_conversion_hpp__ #define __eigenpy_scalar_conversion_hpp__ #include "eigenpy/config.hpp" +#include namespace eigenpy { -template -struct FromTypeToType : public boost::false_type {}; +template +struct FromTypeToType + : public boost::numeric::conversion_traits::subranged {}; -template -struct FromTypeToType : public boost::true_type {}; - -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > : public boost::true_type {}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > : public boost::true_type {}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > - : public boost::true_type {}; - -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > : public boost::true_type {}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > : public boost::true_type {}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > - : public boost::true_type {}; - -template <> -struct FromTypeToType > : public boost::true_type {}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > : public boost::true_type { -}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > - : public boost::true_type {}; - -template <> -struct FromTypeToType > : public boost::true_type { -}; -template <> -struct FromTypeToType : public boost::true_type {}; -template <> -struct FromTypeToType > - : public boost::true_type {}; } // namespace eigenpy #endif // __eigenpy_scalar_conversion_hpp__ diff --git a/include/eigenpy/scipy-allocator.hpp b/include/eigenpy/scipy-allocator.hpp new file mode 100644 index 000000000..848fd62f0 --- /dev/null +++ b/include/eigenpy/scipy-allocator.hpp @@ -0,0 +1,239 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_scipy_allocator_hpp__ +#define __eigenpy_scipy_allocator_hpp__ + +#include "eigenpy/fwd.hpp" +#include "eigenpy/eigen-allocator.hpp" +#include "eigenpy/scipy-type.hpp" +#include "eigenpy/register.hpp" + +namespace eigenpy { + +template +struct scipy_allocator_impl; + +template +struct scipy_allocator_impl_sparse_matrix; + +template +struct scipy_allocator_impl< + MatType, + Eigen::SparseMatrixBase::type> > + : scipy_allocator_impl_sparse_matrix {}; + +template +struct scipy_allocator_impl< + const MatType, const Eigen::SparseMatrixBase< + typename remove_const_reference::type> > + : scipy_allocator_impl_sparse_matrix {}; + +// template +// struct scipy_allocator_impl > : +// scipy_allocator_impl_sparse_matrix +//{}; + +template +struct scipy_allocator_impl > + : scipy_allocator_impl_sparse_matrix {}; + +template ::type> +struct ScipyAllocator : scipy_allocator_impl {}; + +template +struct scipy_allocator_impl_sparse_matrix { + template + static PyObject *allocate( + const Eigen::SparseCompressedBase &mat_, + bool copy = false) { + EIGENPY_UNUSED_VARIABLE(copy); + typedef typename SimilarMatrixType::Scalar Scalar; + typedef typename SimilarMatrixType::StorageIndex StorageIndex; + + enum { IsRowMajor = SimilarMatrixType::IsRowMajor }; + + typedef Eigen::Matrix DataVector; + typedef const Eigen::Map MapDataVector; + typedef Eigen::Matrix StorageIndexVector; + typedef Eigen::Matrix ScipyStorageIndexVector; + typedef const Eigen::Map MapStorageIndexVector; + + SimilarMatrixType &mat = mat_.const_cast_derived(); + bp::object scipy_sparse_matrix_type = + ScipyType::get_pytype_object(); + + MapDataVector data(mat.valuePtr(), mat.nonZeros()); + MapStorageIndexVector outer_indices( + mat.outerIndexPtr(), (IsRowMajor ? mat.rows() : mat.cols()) + 1); + MapStorageIndexVector inner_indices(mat.innerIndexPtr(), mat.nonZeros()); + + bp::object scipy_sparse_matrix; + + if (mat.rows() == 0 && + mat.cols() == 0) // handle the specific case of empty matrix + { + // PyArray_Descr* npy_type = + // Register::getPyArrayDescrFromScalarType(); bp::dict args; + // args["dtype"] = + // bp::object(bp::handle<>(bp::borrowed(npy_type->typeobj))); + // args["shape"] = bp::object(bp::handle<>(bp::borrowed(Py_None))); + // scipy_sparse_matrix = + // scipy_sparse_matrix_type(*bp::make_tuple(0,0),**args); + scipy_sparse_matrix = scipy_sparse_matrix_type( + Eigen::Matrix(0, 0)); + } else { + scipy_sparse_matrix = scipy_sparse_matrix_type(bp::make_tuple( + DataVector(data), + ScipyStorageIndexVector(inner_indices.template cast()), + ScipyStorageIndexVector( + outer_indices.template cast()))); //, + // bp::make_tuple(mat.rows(), + // mat.cols()))); + } + Py_INCREF(scipy_sparse_matrix.ptr()); + return scipy_sparse_matrix.ptr(); + } +}; + +// template +// struct scipy_allocator_impl_sparse_matrix { +// template +// static PyArrayObject *allocate(Eigen::PlainObjectBase +// &mat, +// npy_intp nd, npy_intp *shape) { +// typedef typename SimilarMatrixType::Scalar Scalar; +// enum { +// NPY_ARRAY_MEMORY_CONTIGUOUS = +// SimilarMatrixType::IsRowMajor ? NPY_ARRAY_CARRAY : NPY_ARRAY_FARRAY +// }; +// +// if (NumpyType::sharedMemory()) { +// const int Scalar_type_code = Register::getTypeCode(); +// PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New( +// getPyArrayType(), static_cast(nd), shape, Scalar_type_code, +// mat.data(), NPY_ARRAY_MEMORY_CONTIGUOUS | NPY_ARRAY_ALIGNED); +// +// return pyArray; +// } else { +// return NumpyAllocator::allocate(mat, nd, shape); +// } +// } +// }; + +#if EIGEN_VERSION_AT_LEAST(3, 2, 0) + +// template +// struct scipy_allocator_impl_sparse_matrix > { +// typedef Eigen::Ref RefType; +// +// static PyArrayObject *allocate(RefType &mat, npy_intp nd, npy_intp *shape) +// { +// typedef typename RefType::Scalar Scalar; +// enum { +// NPY_ARRAY_MEMORY_CONTIGUOUS = +// RefType::IsRowMajor ? NPY_ARRAY_CARRAY : NPY_ARRAY_FARRAY +// }; +// +// if (NumpyType::sharedMemory()) { +// const int Scalar_type_code = Register::getTypeCode(); +// const bool reverse_strides = MatType::IsRowMajor || (mat.rows() == 1); +// Eigen::DenseIndex inner_stride = reverse_strides ? mat.outerStride() +// : mat.innerStride(), +// outer_stride = reverse_strides ? mat.innerStride() +// : mat.outerStride(); +// +// const int elsize = +// call_PyArray_DescrFromType(Scalar_type_code)->elsize; npy_intp +// strides[2] = {elsize * inner_stride, elsize * outer_stride}; +// +// PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New( +// getPyArrayType(), static_cast(nd), shape, Scalar_type_code, +// strides, mat.data(), NPY_ARRAY_MEMORY_CONTIGUOUS | +// NPY_ARRAY_ALIGNED); +// +// return pyArray; +// } else { +// return NumpyAllocator::allocate(mat, nd, shape); +// } +// } +// }; + +#endif + +// template +// struct scipy_allocator_impl_sparse_matrix { +// template +// static PyArrayObject *allocate( +// const Eigen::PlainObjectBase &mat, npy_intp nd, +// npy_intp *shape) { +// typedef typename SimilarMatrixType::Scalar Scalar; +// enum { +// NPY_ARRAY_MEMORY_CONTIGUOUS_RO = SimilarMatrixType::IsRowMajor +// ? NPY_ARRAY_CARRAY_RO +// : NPY_ARRAY_FARRAY_RO +// }; +// +// if (NumpyType::sharedMemory()) { +// const int Scalar_type_code = Register::getTypeCode(); +// PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New( +// getPyArrayType(), static_cast(nd), shape, Scalar_type_code, +// const_cast(mat.data()), +// NPY_ARRAY_MEMORY_CONTIGUOUS_RO | NPY_ARRAY_ALIGNED); +// +// return pyArray; +// } else { +// return NumpyAllocator::allocate(mat, nd, shape); +// } +// } +// }; + +#if EIGEN_VERSION_AT_LEAST(3, 2, 0) + +// template +// struct scipy_allocator_impl_sparse_matrix< +// const Eigen::Ref > { +// typedef const Eigen::Ref RefType; +// +// static PyArrayObject *allocate(RefType &mat, npy_intp nd, npy_intp *shape) +// { +// typedef typename RefType::Scalar Scalar; +// enum { +// NPY_ARRAY_MEMORY_CONTIGUOUS_RO = +// RefType::IsRowMajor ? NPY_ARRAY_CARRAY_RO : NPY_ARRAY_FARRAY_RO +// }; +// +// if (NumpyType::sharedMemory()) { +// const int Scalar_type_code = Register::getTypeCode(); +// +// const bool reverse_strides = MatType::IsRowMajor || (mat.rows() == 1); +// Eigen::DenseIndex inner_stride = reverse_strides ? mat.outerStride() +// : mat.innerStride(), +// outer_stride = reverse_strides ? mat.innerStride() +// : mat.outerStride(); +// +// const int elsize = +// call_PyArray_DescrFromType(Scalar_type_code)->elsize; npy_intp +// strides[2] = {elsize * inner_stride, elsize * outer_stride}; +// +// PyArrayObject *pyArray = (PyArrayObject *)call_PyArray_New( +// getPyArrayType(), static_cast(nd), shape, Scalar_type_code, +// strides, const_cast(mat.data()), +// NPY_ARRAY_MEMORY_CONTIGUOUS_RO | NPY_ARRAY_ALIGNED); +// +// return pyArray; +// } else { +// return NumpyAllocator::allocate(mat, nd, shape); +// } +// } +// }; + +#endif + +} // namespace eigenpy + +#endif // ifndef __eigenpy_scipy_allocator_hpp__ diff --git a/include/eigenpy/scipy-type.hpp b/include/eigenpy/scipy-type.hpp new file mode 100644 index 000000000..7d53e1148 --- /dev/null +++ b/include/eigenpy/scipy-type.hpp @@ -0,0 +1,69 @@ +/* + * Copyright 2024 INRIA + */ + +#ifndef __eigenpy_scipy_type_hpp__ +#define __eigenpy_scipy_type_hpp__ + +#include "eigenpy/fwd.hpp" +#include "eigenpy/register.hpp" +#include "eigenpy/scalar-conversion.hpp" +#include "eigenpy/numpy-type.hpp" + +namespace eigenpy { + +struct EIGENPY_DLLAPI ScipyType { + static ScipyType& getInstance(); + + static void sharedMemory(const bool value); + + static bool sharedMemory(); + + static bp::object getScipyType(); + + static const PyTypeObject* getScipyCSRMatrixType(); + static const PyTypeObject* getScipyCSCMatrixType(); + + template + static bp::object get_pytype_object( + const Eigen::SparseMatrixBase* ptr = nullptr) { + EIGENPY_UNUSED_VARIABLE(ptr); + return SparseMatrix::IsRowMajor ? getInstance().csr_matrix_obj + : getInstance().csc_matrix_obj; + } + + template + static PyTypeObject const* get_pytype( + const Eigen::SparseMatrixBase* ptr = nullptr) { + EIGENPY_UNUSED_VARIABLE(ptr); + return SparseMatrix::IsRowMajor ? getInstance().csr_matrix_type + : getInstance().csc_matrix_type; + } + + static int get_numpy_type_num(const bp::object& obj) { + const PyTypeObject* type = Py_TYPE(obj.ptr()); + EIGENPY_USED_VARIABLE_ONLY_IN_DEBUG_MODE(type); + assert(type == getInstance().csr_matrix_type || + type == getInstance().csc_matrix_type); + + bp::object dtype = obj.attr("dtype"); + + const PyArray_Descr* npy_type = + reinterpret_cast(dtype.ptr()); + return npy_type->type_num; + } + + protected: + ScipyType(); + + bp::object sparse_module; + + // SciPy types + bp::object csr_matrix_obj, csc_matrix_obj; + PyTypeObject *csr_matrix_type, *csc_matrix_type; + + bool shared_memory; +}; +} // namespace eigenpy + +#endif // ifndef __eigenpy_scipy_type_hpp__ diff --git a/include/eigenpy/sparse/eigen-from-python.hpp b/include/eigenpy/sparse/eigen-from-python.hpp new file mode 100644 index 000000000..f5158a53f --- /dev/null +++ b/include/eigenpy/sparse/eigen-from-python.hpp @@ -0,0 +1,267 @@ +// +// Copyright (c) 2024 INRIA +// + +#ifndef __eigenpy_sparse_eigen_from_python_hpp__ +#define __eigenpy_sparse_eigen_from_python_hpp__ + +#include "eigenpy/fwd.hpp" +#include "eigenpy/eigen-allocator.hpp" +#include "eigenpy/scipy-type.hpp" +#include "eigenpy/scalar-conversion.hpp" + +namespace eigenpy { + +template +struct expected_pytype_for_arg > { + static PyTypeObject const *get_pytype() { + PyTypeObject const *py_type = ScipyType::get_pytype(); + return py_type; + } +}; + +} // namespace eigenpy + +namespace boost { +namespace python { +namespace converter { + +template +struct expected_pytype_for_arg< + Eigen::SparseMatrix > + : eigenpy::expected_pytype_for_arg< + Eigen::SparseMatrix > {}; + +template +struct rvalue_from_python_data< + Eigen::SparseMatrix const &> + : ::eigenpy::rvalue_from_python_data< + Eigen::SparseMatrix const &> { + typedef Eigen::SparseMatrix T; + EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(T const &) +}; + +template +struct rvalue_from_python_data const &> + : ::eigenpy::rvalue_from_python_data { + EIGENPY_RVALUE_FROM_PYTHON_DATA_INIT(Derived const &) +}; + +} // namespace converter +} // namespace python +} // namespace boost + +namespace boost { +namespace python { +namespace detail { +// template +// struct referent_storage &> { +// typedef Eigen::TensorRef RefType; +// typedef ::eigenpy::details::referent_storage_eigen_ref +// StorageType; typedef typename ::eigenpy::aligned_storage< +// referent_size::value>::type type; +// }; + +// template +// struct referent_storage &> { +// typedef Eigen::TensorRef RefType; +// typedef ::eigenpy::details::referent_storage_eigen_ref +// StorageType; typedef typename ::eigenpy::aligned_storage< +// referent_size::value>::type type; +// }; +} // namespace detail +} // namespace python +} // namespace boost + +namespace eigenpy { + +template +struct eigen_from_py_impl > { + typedef typename SparseMatrixType::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 +void *eigen_from_py_impl< + SparseMatrixType, + Eigen::SparseMatrixBase >::convertible(PyObject *pyObj) { + const PyTypeObject *type = Py_TYPE(pyObj); + const PyTypeObject *sparse_matrix_py_type = + ScipyType::get_pytype(); + typedef typename SparseMatrixType::Scalar Scalar; + + if (type != sparse_matrix_py_type) return 0; + + bp::object obj(bp::handle<>(bp::borrowed(pyObj))); + + const int type_num = ScipyType::get_numpy_type_num(obj); + + if (!np_type_is_convertible_into_scalar(type_num)) return 0; + + return pyObj; +} + +template +void eigen_sparse_matrix_from_py_construct( + PyObject *pyObj, bp::converter::rvalue_from_python_stage1_data *memory) { + typedef typename MatOrRefType::Scalar Scalar; + typedef typename MatOrRefType::StorageIndex StorageIndex; + + typedef Eigen::Map MapMatOrRefType; + + bp::converter::rvalue_from_python_storage *storage = + reinterpret_cast< + bp::converter::rvalue_from_python_storage *>( + reinterpret_cast(memory)); + void *raw_ptr = storage->storage.bytes; + + bp::object obj(bp::handle<>(bp::borrowed(pyObj))); + + const int type_num_python_sparse_matrix = ScipyType::get_numpy_type_num(obj); + const int type_num_eigen_sparse_matrix = Register::getTypeCode(); + + if (type_num_eigen_sparse_matrix == type_num_python_sparse_matrix) { + typedef Eigen::Matrix DataVector; + // typedef const Eigen::Ref RefDataVector; + DataVector data = bp::extract(obj.attr("data")); + bp::tuple shape = bp::extract(obj.attr("shape")); + typedef Eigen::Matrix StorageIndexVector; + // typedef const Eigen::Ref + // RefStorageIndexVector; + StorageIndexVector indices = + bp::extract(obj.attr("indices")); + StorageIndexVector indptr = + bp::extract(obj.attr("indptr")); + + const Eigen::Index m = bp::extract(shape[0]), + n = bp::extract(shape[1]), + nnz = bp::extract(obj.attr("nnz")); + MapMatOrRefType sparse_map(m, n, nnz, indptr.data(), indices.data(), + data.data()); + + new (raw_ptr) MatOrRefType(sparse_map); + } + + memory->convertible = storage->storage.bytes; +} + +template +void eigen_from_py_impl >:: + construct(PyObject *pyObj, + bp::converter::rvalue_from_python_stage1_data *memory) { + eigen_sparse_matrix_from_py_construct(pyObj, memory); +} + +template +void eigen_from_py_impl< + SparseMatrixType, + Eigen::SparseMatrixBase >::registration() { + bp::converter::registry::push_back( + reinterpret_cast(&eigen_from_py_impl::convertible), + &eigen_from_py_impl::construct, bp::type_id() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , + &eigenpy::expected_pytype_for_arg::get_pytype +#endif + ); +} + +template +struct eigen_from_py_converter_impl< + SparseMatrixType, Eigen::SparseMatrixBase > { + static void registration() { + EigenFromPy::registration(); + + // Add conversion to Eigen::SparseMatrixBase + typedef Eigen::SparseMatrixBase SparseMatrixBase; + EigenFromPy::registration(); + + // // Add conversion to Eigen::Ref + // typedef Eigen::Ref RefType; + // EigenFromPy::registration(); + // + // // Add conversion to Eigen::Ref + // typedef const Eigen::Ref ConstRefType; + // EigenFromPy::registration(); + } +}; + +template +struct EigenFromPy > + : EigenFromPy { + typedef EigenFromPy EigenFromPyDerived; + typedef Eigen::SparseMatrixBase Base; + + static void registration() { + bp::converter::registry::push_back( + reinterpret_cast(&EigenFromPy::convertible), + &EigenFromPy::construct, bp::type_id() +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + , + &eigenpy::expected_pytype_for_arg::get_pytype +#endif + ); + } +}; +// +// template +// struct EigenFromPy > { +// typedef Eigen::TensorRef 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(pyObj); +// if (!PyArray_ISWRITEABLE(pyArray)) return 0; +// return EigenFromPy::convertible(pyObj); +// } +// +// static void registration() { +// bp::converter::registry::push_back( +// reinterpret_cast(&EigenFromPy::convertible), +// &eigen_from_py_construct, bp::type_id() +// #ifndef BOOST_PYTHON_NO_PY_SIGNATURES +// , +// &eigenpy::expected_pytype_for_arg::get_pytype +// #endif +// ); +// } +//}; + +// template +// struct EigenFromPy > { +// typedef const Eigen::TensorRef ConstRefType; +// typedef typename TensorType::Scalar Scalar; +// +// /// \brief Determine if pyObj can be converted into a MatType object +// static void *convertible(PyObject *pyObj) { +// return EigenFromPy::convertible(pyObj); +// } +// +// static void registration() { +// bp::converter::registry::push_back( +// reinterpret_cast(&EigenFromPy::convertible), +// &eigen_from_py_construct, bp::type_id() +// #ifndef BOOST_PYTHON_NO_PY_SIGNATURES +// , +// &eigenpy::expected_pytype_for_arg::get_pytype +// #endif +// ); +// } +// }; + +} // namespace eigenpy + +#endif // __eigenpy_sparse_eigen_from_python_hpp__ diff --git a/src/matrix-long.cpp b/src/matrix-long.cpp index db29afd4b..f52e89f1a 100644 --- a/src/matrix-long.cpp +++ b/src/matrix-long.cpp @@ -6,7 +6,12 @@ namespace eigenpy { void exposeMatrixLong() { +#ifdef WIN32 + exposeType<__int64>(); + exposeType<__int64, Eigen::RowMajor>(); +#else exposeType(); exposeType(); +#endif } } // namespace eigenpy diff --git a/src/register.cpp b/src/register.cpp index ec22decec..84e84f645 100644 --- a/src/register.cpp +++ b/src/register.cpp @@ -14,6 +14,17 @@ PyArray_Descr* Register::getPyArrayDescr(PyTypeObject* py_type_ptr) { return NULL; } +PyArray_Descr* Register::getPyArrayDescrFromTypeNum(const int type_num) { + if (type_num >= NPY_USERDEF) { + for (const auto& elt : instance().py_array_code_bindings) { + if (elt.second == type_num) + return instance().py_array_descr_bindings[elt.first]; + } + return nullptr; + } else + return PyArray_DescrFromType(type_num); +} + bool Register::isRegistered(PyTypeObject* py_type_ptr) { if (getPyArrayDescr(py_type_ptr) != NULL) return true; diff --git a/src/scipy-type.cpp b/src/scipy-type.cpp new file mode 100644 index 000000000..e189e636f --- /dev/null +++ b/src/scipy-type.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2024 INRIA + */ + +#include "eigenpy/scipy-type.hpp" + +#include // For PY_MAJOR_VERSION + +namespace eigenpy { + +ScipyType& ScipyType::getInstance() { + static ScipyType instance; + return instance; +} + +void ScipyType::sharedMemory(const bool value) { + getInstance().shared_memory = value; +} + +bool ScipyType::sharedMemory() { return getInstance().shared_memory; } + +const PyTypeObject* ScipyType::getScipyCSRMatrixType() { + return getInstance().csr_matrix_type; +} + +const PyTypeObject* ScipyType::getScipyCSCMatrixType() { + return getInstance().csc_matrix_type; +} + +ScipyType::ScipyType() { + sparse_module = bp::import("scipy.sparse"); + +#if PY_MAJOR_VERSION >= 3 + // TODO I don't know why this Py_INCREF is necessary. + // Without it, the destructor of ScipyType SEGV sometimes. + Py_INCREF(sparse_module.ptr()); +#endif + + csr_matrix_obj = sparse_module.attr("csr_matrix"); + csr_matrix_type = reinterpret_cast(csr_matrix_obj.ptr()); + csc_matrix_obj = sparse_module.attr("csc_matrix"); + csc_matrix_type = reinterpret_cast(csc_matrix_obj.ptr()); + + shared_memory = true; +} +} // namespace eigenpy diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 88b07f496..dab2bfba7 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2014-2019 CNRS Copyright (c) 2018-2023 INRIA +# Copyright (c) 2014-2019 CNRS Copyright (c) 2018-2024 INRIA # macro(ADD_LIB_UNIT_TEST test) @@ -25,6 +25,10 @@ macro(ADD_LIB_UNIT_TEST test) endmacro(ADD_LIB_UNIT_TEST) add_lib_unit_test(matrix) +if(BUILD_TESTING_SCIPY) + find_scipy() + add_lib_unit_test(sparse_matrix) +endif() add_lib_unit_test(tensor) add_lib_unit_test(geometry) add_lib_unit_test(complex) @@ -63,6 +67,10 @@ endif() add_lib_unit_test(bind_virtual_factory) add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest") +if(BUILD_TESTING_SCIPY) + add_python_unit_test("py-sparse-matrix" + "unittest/python/test_sparse_matrix.py" "unittest") +endif() add_python_unit_test("py-tensor" "unittest/python/test_tensor.py" "unittest") add_python_unit_test("py-geometry" "unittest/python/test_geometry.py" diff --git a/unittest/python/test_sparse_matrix.py b/unittest/python/test_sparse_matrix.py new file mode 100644 index 000000000..428c3bede --- /dev/null +++ b/unittest/python/test_sparse_matrix.py @@ -0,0 +1,28 @@ +from __future__ import print_function + +import numpy as np +import sparse_matrix +from scipy.sparse import csc_matrix, csr_matrix + +m = sparse_matrix.emptyMatrix() +assert m.shape == (0, 0) + +v = sparse_matrix.emptyVector() +assert v.shape == (0, 0) + +m = sparse_matrix.matrix1x1(2) +assert m.toarray() == np.array([2]) + +v = sparse_matrix.vector1x1(2) +assert v.toarray() == np.array([2]) + +size = 100 +diag_values = np.random.rand(100) +diag_mat = sparse_matrix.diagonal(diag_values) +assert (diag_mat.toarray() == np.diag(diag_values)).all() + +diag_mat_copy = sparse_matrix.copy(diag_mat) +assert (diag_mat_copy != diag_mat).nnz == 0 + +diag_mat_csr = csr_matrix(diag_mat) +assert (sparse_matrix.copy(diag_mat_csr) != diag_mat_csr).nnz == 0 diff --git a/unittest/python/test_tensor.py b/unittest/python/test_tensor.py index b82d34f91..ff641cfec 100644 --- a/unittest/python/test_tensor.py +++ b/unittest/python/test_tensor.py @@ -3,7 +3,7 @@ import numpy as np import tensor -dim = np.array([10, 20, 30], dtype=np.int32) +dim = np.array([10, 20, 30], dtype=np.int64) t = tensor.TensorContainer3(dim) r = t.get_ref() r[:] = 0.0 diff --git a/unittest/sparse_matrix.cpp b/unittest/sparse_matrix.cpp new file mode 100644 index 000000000..e8e4b2d60 --- /dev/null +++ b/unittest/sparse_matrix.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2024 CNRS INRIA + */ + +#include + +#include "eigenpy/eigenpy.hpp" + +template +Eigen::SparseMatrix vector1x1(const Scalar& value) { + typedef Eigen::SparseMatrix ReturnType; + ReturnType mat(1, 1); + mat.coeffRef(0, 0) = value; + mat.makeCompressed(); + return mat; +} + +template +Eigen::SparseMatrix matrix1x1(const Scalar& value) { + typedef Eigen::SparseMatrix ReturnType; + ReturnType mat(1, 1); + mat.coeffRef(0, 0) = value; + mat.makeCompressed(); + return mat; +} + +template +Eigen::SparseMatrix diagonal( + const Eigen::Ref >& diag_values) { + typedef Eigen::SparseMatrix ReturnType; + ReturnType mat(diag_values.size(), diag_values.size()); + for (Eigen::Index k = 0; k < diag_values.size(); ++k) + mat.coeffRef(k, k) = diag_values[k]; + mat.makeCompressed(); + return mat; +} + +template +Eigen::SparseMatrix emptyVector() { + return Eigen::SparseMatrix(); +} + +template +Eigen::SparseMatrix emptyMatrix() { + return Eigen::SparseMatrix(); +} + +template +void print(const Eigen::SparseMatrix& mat) { + std::cout << mat << std::endl; +} + +template +Eigen::SparseMatrix copy( + const Eigen::SparseMatrix& mat) { + return mat; +} + +template +void expose_functions() { + namespace bp = boost::python; + bp::def("vector1x1", vector1x1); + bp::def("matrix1x1", matrix1x1); + + bp::def("print", print); + bp::def("copy", copy); + bp::def("diagonal", diagonal); + + bp::def("emptyVector", emptyVector); + bp::def("emptyMatrix", emptyMatrix); +} + +BOOST_PYTHON_MODULE(sparse_matrix) { + namespace bp = boost::python; + eigenpy::enableEigenPy(); + + expose_functions(); + expose_functions(); +} diff --git a/unittest/tensor.cpp b/unittest/tensor.cpp index 80838ebfc..7a7ef6d3e 100644 --- a/unittest/tensor.cpp +++ b/unittest/tensor.cpp @@ -122,7 +122,7 @@ template struct TensorContainer { typedef Eigen::Tensor Tensor; typedef Eigen::TensorRef TensorRef; - typedef Eigen::Matrix Dimensions; + typedef Eigen::Matrix Dimensions; Tensor m_tensor; TensorContainer(const Dimensions& dims) {