From 082e22e285af43d1ce3685e6597ec2098654b9cd Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 22 Jan 2024 12:59:41 +0100 Subject: [PATCH 01/31] test/tensor: fix type --- unittest/tensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From fb42538709d92b2881f4ebd5c7d6425f4f508f18 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Mon, 22 Jan 2024 13:01:07 +0100 Subject: [PATCH 02/31] core: remove non-implemented method --- include/eigenpy/numpy-type.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/eigenpy/numpy-type.hpp b/include/eigenpy/numpy-type.hpp index 816390b47..eac7e7abf 100644 --- a/include/eigenpy/numpy-type.hpp +++ b/include/eigenpy/numpy-type.hpp @@ -56,8 +56,6 @@ struct EIGENPY_DLLAPI NumpyType { static bool sharedMemory(); - static bp::object getNumpyType(); - static const PyTypeObject* getNumpyArrayType(); protected: From c157582dfe88d2b4c5ede44f0c365cfe31f28af2 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 23 Jan 2024 09:34:42 +0100 Subject: [PATCH 03/31] core: change helper name --- include/eigenpy/eigen-from-python.hpp | 2 +- include/eigenpy/fwd.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/eigenpy/eigen-from-python.hpp b/include/eigenpy/eigen-from-python.hpp index f31e3512e..36c755616 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; diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp index aada9ac1c..21c3a485b 100644 --- a/include/eigenpy/fwd.hpp +++ b/include/eigenpy/fwd.hpp @@ -156,17 +156,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 From fc43556bd28b18379dd3beee24722d9cb31ace4c Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 23 Jan 2024 09:35:40 +0100 Subject: [PATCH 04/31] core: start exposing Eigen::SparseMatrix --- include/eigenpy/fwd.hpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp index 21c3a485b..698bc5c73 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 @@ -138,17 +139,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>, From c4b7433f21a694f8a8ccd835f222af62d67f0bb1 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 23 Jan 2024 09:38:09 +0100 Subject: [PATCH 05/31] cmake: sync submodule --- cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake b/cmake index 24abb7409..935e2154e 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 24abb7409c89f3c7fb08e55cf3edc823781ff9fb +Subproject commit 935e2154e02e84f147046278bfd64e27a5f0850a From cbef828c8c402468325dd82ad5ea40a96fa7b22f Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 23 Jan 2024 09:50:04 +0100 Subject: [PATCH 06/31] sparse: add sicpy-type --- CMakeLists.txt | 2 ++ include/eigenpy/scipy-type.hpp | 48 ++++++++++++++++++++++++++++++++++ src/scipy-type.cpp | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 include/eigenpy/scipy-type.hpp create mode 100644 src/scipy-type.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a280328c6..7dc11a1e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,7 @@ set(${PROJECT_NAME}_HEADERS include/eigenpy/pickle-vector.hpp include/eigenpy/stride.hpp include/eigenpy/tensor/eigen-from-python.hpp + include/eigenpy/scipy-type.hpp include/eigenpy/swig.hpp include/eigenpy/version.hpp) @@ -209,6 +210,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/include/eigenpy/scipy-type.hpp b/include/eigenpy/scipy-type.hpp new file mode 100644 index 000000000..998b10fc9 --- /dev/null +++ b/include/eigenpy/scipy-type.hpp @@ -0,0 +1,48 @@ +/* + * 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; + } + + 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/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 From 46f0d640b8a8e65d46288237b2de75e953f6bf66 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Tue, 23 Jan 2024 09:50:30 +0100 Subject: [PATCH 07/31] cmake: call find_scipy --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dc11a1e5..f00d4a074 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,8 @@ if(${NUMPY_VERSION} VERSION_LESS "1.16.0") set(NUMPY_WITH_BROKEN_UFUNC_SUPPORT TRUE) endif() +find_scipy() + if(WIN32) link_directories(${PYTHON_LIBRARY_DIRS}) # # Set default Windows build paths SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY From 64f012a5b8b6d855ea8882b4c6c2ec51d3cd8f43 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Thu, 25 Jan 2024 13:58:13 +0100 Subject: [PATCH 08/31] core: add helper macros --- include/eigenpy/fwd.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/eigenpy/fwd.hpp b/include/eigenpy/fwd.hpp index 698bc5c73..7f5294995 100644 --- a/include/eigenpy/fwd.hpp +++ b/include/eigenpy/fwd.hpp @@ -109,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 From a2484d769621425c2e05969d7abbbc9a7f85d8f7 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Thu, 25 Jan 2024 13:58:49 +0100 Subject: [PATCH 09/31] sparse: add first working version for Eigen::SparseMatrix --- CMakeLists.txt | 2 + include/eigenpy/eigen-from-python.hpp | 2 + include/eigenpy/eigen-to-python.hpp | 51 +++- include/eigenpy/scipy-allocator.hpp | 225 ++++++++++++++++ include/eigenpy/scipy-type.hpp | 21 ++ include/eigenpy/sparse/eigen-from-python.hpp | 267 +++++++++++++++++++ 6 files changed, 566 insertions(+), 2 deletions(-) create mode 100644 include/eigenpy/scipy-allocator.hpp create mode 100644 include/eigenpy/sparse/eigen-from-python.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f00d4a074..f995f03fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,8 @@ 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) diff --git a/include/eigenpy/eigen-from-python.hpp b/include/eigenpy/eigen-from-python.hpp index 36c755616..904c2dd9c 100644 --- a/include/eigenpy/eigen-from-python.hpp +++ b/include/eigenpy/eigen-from-python.hpp @@ -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/scipy-allocator.hpp b/include/eigenpy/scipy-allocator.hpp new file mode 100644 index 000000000..a5cc28551 --- /dev/null +++ b/include/eigenpy/scipy-allocator.hpp @@ -0,0 +1,225 @@ +/* + * 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" + +#include + +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 = 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 index 998b10fc9..7d53e1148 100644 --- a/include/eigenpy/scipy-type.hpp +++ b/include/eigenpy/scipy-type.hpp @@ -32,6 +32,27 @@ struct EIGENPY_DLLAPI ScipyType { : 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(); diff --git a/include/eigenpy/sparse/eigen-from-python.hpp b/include/eigenpy/sparse/eigen-from-python.hpp new file mode 100644 index 000000000..d45b6fd98 --- /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" +#include + +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")); + + MapMatOrRefType map(bp::extract(shape[0]), + bp::extract(shape[1]), + bp::extract(obj.attr("nnz")), + indptr.data(), indices.data(), data.data()); + + new (raw_ptr) MatOrRefType(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__ From b90938849a5bb9fa8a07345152a2d5d99ccde948 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Thu, 25 Jan 2024 15:37:25 +0100 Subject: [PATCH 10/31] ci: add scipy dependency --- .github/workflows/conda/environment_macos_linux.yml | 1 + .github/workflows/conda/environment_windows.yml | 1 + .github/workflows/linux.yml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) 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/linux.yml b/.github/workflows/linux.yml index 94d5cd074..48a8d17a2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,7 +16,7 @@ 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 }}) . From 94e47a4faa44b9d1f19228171640507aa537774c Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Thu, 25 Jan 2024 15:39:31 +0100 Subject: [PATCH 11/31] changelog: update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f5eb9c7..049761917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ 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)) + ## [3.3.0] - 2024-01-23 ### Fixed From fdf2a99958b03566a8e81da857ce1c2c960f6cb1 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:28:07 +0100 Subject: [PATCH 12/31] core: add helpers in Register --- include/eigenpy/register.hpp | 19 +++++++++++++++++++ src/register.cpp | 11 +++++++++++ 2 files changed, 30 insertions(+) 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/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; From b2f20b041aa3d410f97d04e6dee01347628a11f5 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:28:33 +0100 Subject: [PATCH 13/31] sparse: fix issue for 0-sized matrices --- include/eigenpy/scipy-allocator.hpp | 30 ++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/include/eigenpy/scipy-allocator.hpp b/include/eigenpy/scipy-allocator.hpp index a5cc28551..786539467 100644 --- a/include/eigenpy/scipy-allocator.hpp +++ b/include/eigenpy/scipy-allocator.hpp @@ -73,13 +73,29 @@ struct scipy_allocator_impl_sparse_matrix { mat.outerIndexPtr(), (IsRowMajor ? mat.rows() : mat.cols()) + 1); MapStorageIndexVector inner_indices(mat.innerIndexPtr(), mat.nonZeros()); - bp::object 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()))); - + 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(); } From 77c8684c2506e9f2aa32869abd3746fe2d560602 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:32:51 +0100 Subject: [PATCH 14/31] sparse: fix potential compilation issues with GCC --- include/eigenpy/sparse/eigen-from-python.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/eigenpy/sparse/eigen-from-python.hpp b/include/eigenpy/sparse/eigen-from-python.hpp index d45b6fd98..9b00aecbe 100644 --- a/include/eigenpy/sparse/eigen-from-python.hpp +++ b/include/eigenpy/sparse/eigen-from-python.hpp @@ -144,12 +144,13 @@ void eigen_sparse_matrix_from_py_construct( StorageIndexVector indptr = bp::extract(obj.attr("indptr")); - MapMatOrRefType map(bp::extract(shape[0]), - bp::extract(shape[1]), - bp::extract(obj.attr("nnz")), - indptr.data(), indices.data(), data.data()); + 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(map); + new (raw_ptr) MatOrRefType(sparse_map); } memory->convertible = storage->storage.bytes; From df49d6ee9d8bde6faacf4ed16590bc53bff07d28 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:33:12 +0100 Subject: [PATCH 15/31] test/sparse: add minimal test --- unittest/CMakeLists.txt | 3 +- unittest/python/test_sparse_matrix.py | 24 ++++++++ unittest/sparse_matrix.cpp | 84 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 unittest/python/test_sparse_matrix.py create mode 100644 unittest/sparse_matrix.cpp diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 88b07f496..404fe9dca 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,7 @@ macro(ADD_LIB_UNIT_TEST test) endmacro(ADD_LIB_UNIT_TEST) add_lib_unit_test(matrix) +add_lib_unit_test(sparse_matrix) add_lib_unit_test(tensor) add_lib_unit_test(geometry) add_lib_unit_test(complex) diff --git a/unittest/python/test_sparse_matrix.py b/unittest/python/test_sparse_matrix.py new file mode 100644 index 000000000..803ebe7d9 --- /dev/null +++ b/unittest/python/test_sparse_matrix.py @@ -0,0 +1,24 @@ +from __future__ import print_function + +import numpy as np +import sparse_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 diff --git a/unittest/sparse_matrix.cpp b/unittest/sparse_matrix.cpp new file mode 100644 index 000000000..4fb23a61d --- /dev/null +++ b/unittest/sparse_matrix.cpp @@ -0,0 +1,84 @@ +/* + * 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 +void matrix1x1_input(const Eigen::Matrix& mat) { + std::cout << mat << std::endl; +} + +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; +} + +BOOST_PYTHON_MODULE(sparse_matrix) { + using namespace Eigen; + namespace bp = boost::python; + eigenpy::enableEigenPy(); + + typedef Eigen::SparseMatrix SparseMatrixD; + eigenpy::EigenToPyConverter::registration(); + eigenpy::EigenFromPyConverter::registration(); + + bp::def("vector1x1", vector1x1); + bp::def("matrix1x1", matrix1x1); + bp::def("matrix1x1", matrix1x1_input); + bp::def("matrix1x1_int", matrix1x1_input); + + bp::def("print", print); + bp::def("copy", copy); + bp::def("diagonal", diagonal); + + bp::def("emptyVector", emptyVector); + bp::def("emptyMatrix", emptyMatrix); +} From 8b772edaccd8317f3d55e0442dff977edc5fecb5 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:38:05 +0100 Subject: [PATCH 16/31] ci: add missing scipy --- .github/workflows/jrl-cmakemodules.yml | 4 ++-- .github/workflows/reloc.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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 From 83bd12946c08388e42a17fc69b4b833183ddd354 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:42:52 +0100 Subject: [PATCH 17/31] cmake: remove outdated BUILD_UNIT_TESTS option --- CMakeLists.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f995f03fb..2c700560b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,15 +42,6 @@ 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) - include("${JRL_CMAKE_MODULES}/base.cmake") compute_project_args(PROJECT_ARGS LANGUAGES CXX) project(${PROJECT_NAME} ${PROJECT_ARGS}) From 98c91072e9d954473b74be4db369e1eceb06c231 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:53:26 +0100 Subject: [PATCH 18/31] cmake: move scipy check to the tests --- CMakeLists.txt | 4 ++-- unittest/CMakeLists.txt | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c700560b..1c9024257 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +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) +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) @@ -78,8 +80,6 @@ if(${NUMPY_VERSION} VERSION_LESS "1.16.0") set(NUMPY_WITH_BROKEN_UFUNC_SUPPORT TRUE) endif() -find_scipy() - if(WIN32) link_directories(${PYTHON_LIBRARY_DIRS}) # # Set default Windows build paths SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 404fe9dca..dab2bfba7 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -25,7 +25,10 @@ macro(ADD_LIB_UNIT_TEST test) endmacro(ADD_LIB_UNIT_TEST) add_lib_unit_test(matrix) -add_lib_unit_test(sparse_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) @@ -64,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" From 36971dcf769a0dac8d63ad3c7621e9af919ff53e Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:58:18 +0100 Subject: [PATCH 19/31] core: implement expose_eigen_type_impl for sparse matrices --- include/eigenpy/details.hpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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, From 348d6b1297505c6907b6aca2e5a4de5ddb19eb45 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 10:58:36 +0100 Subject: [PATCH 20/31] core: expose Eigen::SparseMatrix Row and Col majors --- include/eigenpy/eigenpy.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/eigenpy/eigenpy.hpp b/include/eigenpy/eigenpy.hpp index 8a6cecc28..ffea8f7f0 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__ @@ -60,6 +60,9 @@ template EIGEN_DONT_INLINE void exposeType() { exposeType(); + enableEigenPySpecific >(); + enableEigenPySpecific >(); + #ifdef EIGENPY_WITH_TENSOR_SUPPORT enableEigenPySpecific >(); enableEigenPySpecific >(); From 103ae3deafc4164f6f44ce12aa47b28d573daa93 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 11:17:55 +0100 Subject: [PATCH 21/31] windows: fix conversion issue --- unittest/python/test_tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 54d4329cf5e06a2d7ea90766488733acc29b8e4a Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 11:31:46 +0100 Subject: [PATCH 22/31] core: fix proper location for enableEigenPySpecific --- include/eigenpy/eigenpy.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/eigenpy/eigenpy.hpp b/include/eigenpy/eigenpy.hpp index ffea8f7f0..f4edf090d 100644 --- a/include/eigenpy/eigenpy.hpp +++ b/include/eigenpy/eigenpy.hpp @@ -54,15 +54,14 @@ EIGEN_DONT_INLINE void exposeType() { ENABLE_SPECIFIC_MATRIX_TYPE(VectorXs); ENABLE_SPECIFIC_MATRIX_TYPE(RowVectorXs); ENABLE_SPECIFIC_MATRIX_TYPE(MatrixXs); + + enableEigenPySpecific >(); } template EIGEN_DONT_INLINE void exposeType() { exposeType(); - enableEigenPySpecific >(); - enableEigenPySpecific >(); - #ifdef EIGENPY_WITH_TENSOR_SUPPORT enableEigenPySpecific >(); enableEigenPySpecific >(); From f9ef66b2d3af601520508c1747126bcd99e29c7b Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 11:35:06 +0100 Subject: [PATCH 23/31] test/sparse: extend test for Row and Col major matrices --- unittest/python/test_sparse_matrix.py | 4 +++ unittest/sparse_matrix.cpp | 35 ++++++++++++--------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/unittest/python/test_sparse_matrix.py b/unittest/python/test_sparse_matrix.py index 803ebe7d9..428c3bede 100644 --- a/unittest/python/test_sparse_matrix.py +++ b/unittest/python/test_sparse_matrix.py @@ -2,6 +2,7 @@ import numpy as np import sparse_matrix +from scipy.sparse import csc_matrix, csr_matrix m = sparse_matrix.emptyMatrix() assert m.shape == (0, 0) @@ -22,3 +23,6 @@ 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/sparse_matrix.cpp b/unittest/sparse_matrix.cpp index 4fb23a61d..e8e4b2d60 100644 --- a/unittest/sparse_matrix.cpp +++ b/unittest/sparse_matrix.cpp @@ -35,11 +35,6 @@ Eigen::SparseMatrix diagonal( return mat; } -template -void matrix1x1_input(const Eigen::Matrix& mat) { - std::cout << mat << std::endl; -} - template Eigen::SparseMatrix emptyVector() { return Eigen::SparseMatrix(); @@ -61,24 +56,24 @@ Eigen::SparseMatrix copy( return mat; } -BOOST_PYTHON_MODULE(sparse_matrix) { - using namespace Eigen; +template +void expose_functions() { namespace bp = boost::python; - eigenpy::enableEigenPy(); + bp::def("vector1x1", vector1x1); + bp::def("matrix1x1", matrix1x1); - typedef Eigen::SparseMatrix SparseMatrixD; - eigenpy::EigenToPyConverter::registration(); - eigenpy::EigenFromPyConverter::registration(); + bp::def("print", print); + bp::def("copy", copy); + bp::def("diagonal", diagonal); - bp::def("vector1x1", vector1x1); - bp::def("matrix1x1", matrix1x1); - bp::def("matrix1x1", matrix1x1_input); - bp::def("matrix1x1_int", matrix1x1_input); + bp::def("emptyVector", emptyVector); + bp::def("emptyMatrix", emptyMatrix); +} - bp::def("print", print); - bp::def("copy", copy); - bp::def("diagonal", diagonal); +BOOST_PYTHON_MODULE(sparse_matrix) { + namespace bp = boost::python; + eigenpy::enableEigenPy(); - bp::def("emptyVector", emptyVector); - bp::def("emptyMatrix", emptyMatrix); + expose_functions(); + expose_functions(); } From eeb955a82f255f27c3eb70b482d7c52bff607202 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 14:18:10 +0100 Subject: [PATCH 24/31] core: use boost::numerics to simplify the code and make it more generic --- include/eigenpy/scalar-conversion.hpp | 62 +++------------------------ 1 file changed, 5 insertions(+), 57 deletions(-) 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__ From 27be5aedf20d6d5a4083e9ae2fdc4ff54837f7d9 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 14:18:22 +0100 Subject: [PATCH 25/31] core: fix special case on windows --- include/eigenpy/numpy-type.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/eigenpy/numpy-type.hpp b/include/eigenpy/numpy-type.hpp index eac7e7abf..a5d12aef2 100644 --- a/include/eigenpy/numpy-type.hpp +++ b/include/eigenpy/numpy-type.hpp @@ -26,6 +26,10 @@ bool np_type_is_convertible_into_scalar(const int np_type) { switch (np_type) { case NPY_INT: return FromTypeToType::value; +#ifdef WIN32 + case NPY_INT64: + return FromTypeToType<__int64, Scalar>::value; +#endif case NPY_LONG: return FromTypeToType::value; case NPY_FLOAT: From 03775d2ee7915c00d9fe1080512104899a034c20 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 14:21:42 +0100 Subject: [PATCH 26/31] ci: activate check of SciPy support --- .github/workflows/linux.yml | 2 +- .github/workflows/macos-linux-conda.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 48a8d17a2..c3c182b81 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -19,7 +19,7 @@ jobs: 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 From 2c12261d3d312d9bff2738f39bfc7fece26f91a7 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 14:58:24 +0100 Subject: [PATCH 27/31] core: fix support of int32 and int64 support on Windows platforms --- include/eigenpy/numpy-type.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/eigenpy/numpy-type.hpp b/include/eigenpy/numpy-type.hpp index a5d12aef2..319fd2abf 100644 --- a/include/eigenpy/numpy-type.hpp +++ b/include/eigenpy/numpy-type.hpp @@ -24,14 +24,20 @@ 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; -#ifdef WIN32 case NPY_INT64: + case NPY_LONGLONG: return FromTypeToType<__int64, Scalar>::value; -#endif +#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: From 359ae0323122e869596a8944a5d331c1aa689f54 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 15:09:16 +0100 Subject: [PATCH 28/31] core: fix compilation issue on Windows include\eigenpy/numpy-type.hpp(32): error C2196: case value 'NPY_LONGLONG' already used --- include/eigenpy/numpy-type.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/eigenpy/numpy-type.hpp b/include/eigenpy/numpy-type.hpp index 319fd2abf..25dee99f2 100644 --- a/include/eigenpy/numpy-type.hpp +++ b/include/eigenpy/numpy-type.hpp @@ -28,7 +28,6 @@ bool np_type_is_convertible_into_scalar(const int np_type) { case NPY_INT: case NPY_LONG: return FromTypeToType::value; - case NPY_INT64: case NPY_LONGLONG: return FromTypeToType<__int64, Scalar>::value; #else From a564a065e0687824964df3c70ad8c8fab6f09d20 Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 16:19:17 +0100 Subject: [PATCH 29/31] core: fix exposition for type long on Windows --- src/matrix-long.cpp | 5 +++++ 1 file changed, 5 insertions(+) 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 From 68dc5e88b47f08a207611b3b13144938d8f3938c Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 16:54:55 +0100 Subject: [PATCH 30/31] core: remove useless includes --- include/eigenpy/scipy-allocator.hpp | 2 -- include/eigenpy/sparse/eigen-from-python.hpp | 1 - 2 files changed, 3 deletions(-) diff --git a/include/eigenpy/scipy-allocator.hpp b/include/eigenpy/scipy-allocator.hpp index 786539467..848fd62f0 100644 --- a/include/eigenpy/scipy-allocator.hpp +++ b/include/eigenpy/scipy-allocator.hpp @@ -10,8 +10,6 @@ #include "eigenpy/scipy-type.hpp" #include "eigenpy/register.hpp" -#include - namespace eigenpy { template diff --git a/include/eigenpy/sparse/eigen-from-python.hpp b/include/eigenpy/sparse/eigen-from-python.hpp index 9b00aecbe..f5158a53f 100644 --- a/include/eigenpy/sparse/eigen-from-python.hpp +++ b/include/eigenpy/sparse/eigen-from-python.hpp @@ -9,7 +9,6 @@ #include "eigenpy/eigen-allocator.hpp" #include "eigenpy/scipy-type.hpp" #include "eigenpy/scalar-conversion.hpp" -#include namespace eigenpy { From f0a536a73a09e770360703804a4ad52ab188494f Mon Sep 17 00:00:00 2001 From: Justin Carpentier Date: Fri, 26 Jan 2024 18:39:33 +0100 Subject: [PATCH 31/31] changelog: update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 049761917..422e29f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 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