diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1150ffc350ff7fff57f1e3749d241a5384bda3e2..137aada35907ad4d19a4795836d8c02f4453e983 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: rev: v15.0.4 hooks: - id: clang-format - args: [--style=Google] + args: ['--style={BasedOnStyle: Google, SortIncludes: false}'] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/CMakeLists.txt b/CMakeLists.txt index cc4b50abfd642c9641329f758295f77fc6fc99f5..da119a1ee92a3113d58f624b5db8f23aa6381436 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ set(${PROJECT_NAME}_HEADERS include/eigenpy/exception.hpp include/eigenpy/scalar-conversion.hpp include/eigenpy/expose.hpp + include/eigenpy/copyable.hpp include/eigenpy/details.hpp include/eigenpy/fwd.hpp include/eigenpy/eigen-allocator.hpp @@ -154,6 +155,9 @@ set(${PROJECT_NAME}_HEADERS include/eigenpy/user-type.hpp include/eigenpy/ufunc.hpp include/eigenpy/register.hpp + include/eigenpy/std-map.hpp + include/eigenpy/std-vector.hpp + include/eigenpy/pickle-vector.hpp include/eigenpy/stride.hpp include/eigenpy/swig.hpp include/eigenpy/version.hpp) @@ -194,6 +198,7 @@ set(${PROJECT_NAME}_SOURCES src/angle-axis.cpp src/quaternion.cpp src/geometry-conversion.cpp + src/std-vector.cpp src/version.cpp) add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES} diff --git a/cmake b/cmake index c1439c9894173c80e44173fd6af17232f20fa2dd..277d1bd8a5491e6235413bd716756249c3922232 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit c1439c9894173c80e44173fd6af17232f20fa2dd +Subproject commit 277d1bd8a5491e6235413bd716756249c3922232 diff --git a/include/eigenpy/copyable.hpp b/include/eigenpy/copyable.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b6d8c5ed21eb0d495a6b5e3a28c99bcc2226ef14 --- /dev/null +++ b/include/eigenpy/copyable.hpp @@ -0,0 +1,30 @@ +// +// Copyright (c) 2016-2021 CNRS INRIA +// + +#ifndef __eigenpy_utils_copyable_hpp__ +#define __eigenpy_utils_copyable_hpp__ + +#include <boost/python.hpp> + +namespace eigenpy { + +namespace bp = boost::python; + +/// +/// \brief Add the Python method copy to allow a copy of this by calling the +/// copy constructor. +/// +template <class C> +struct CopyableVisitor : public bp::def_visitor<CopyableVisitor<C> > { + template <class PyClass> + void visit(PyClass& cl) const { + cl.def("copy", ©, bp::arg("self"), "Returns a copy of *this."); + } + + private: + static C copy(const C& self) { return C(self); } +}; +} // namespace eigenpy + +#endif // ifndef __eigenpy_utils_copyable_hpp__ diff --git a/include/eigenpy/eigen-allocator.hpp b/include/eigenpy/eigen-allocator.hpp index 3cde4dd4de96b23b591070908a9e4627f6200cdf..2ca889dbe2e1e73d2e3be51d8bbd34a127e792c2 100644 --- a/include/eigenpy/eigen-allocator.hpp +++ b/include/eigenpy/eigen-allocator.hpp @@ -232,6 +232,22 @@ struct EigenAllocator { }; #if EIGEN_VERSION_AT_LEAST(3, 2, 0) +/// @brief Check if we need to allocate @tparam MatType to convert @param +/// pyArray. +/// @details do not allocate if: +/// want row-major & data C-contiguous OR +/// want col-major & data F-contiguous OR +/// you want a compile-time vector +/// in these cases, data layout fits desired view layout +template <typename MatType> +inline bool is_arr_layout_compatible_with_mat_type(PyArrayObject *pyArray) { + bool is_array_C_cont = PyArray_IS_C_CONTIGUOUS(pyArray); + bool is_array_F_cont = PyArray_IS_F_CONTIGUOUS(pyArray); + return (MatType::IsRowMajor && is_array_C_cont) || + (!MatType::IsRowMajor && is_array_F_cont) || + MatType::IsVectorAtCompileTime; +} + template <typename MatType, int Options, typename Stride> struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > { typedef Eigen::Ref<MatType, Options, Stride> RefType; @@ -255,16 +271,9 @@ struct EigenAllocator<Eigen::Ref<MatType, Options, Stride> > { const int pyArray_type_code = EIGENPY_GET_PY_ARRAY_TYPE(pyArray); const int Scalar_type_code = Register::getTypeCode<Scalar>(); if (pyArray_type_code != Scalar_type_code) need_to_allocate |= true; - if ((MatType::IsRowMajor && (PyArray_IS_C_CONTIGUOUS(pyArray) && - !PyArray_IS_F_CONTIGUOUS(pyArray))) || - (!MatType::IsRowMajor && (PyArray_IS_F_CONTIGUOUS(pyArray) && - !PyArray_IS_C_CONTIGUOUS(pyArray))) || - MatType::IsVectorAtCompileTime || - (PyArray_IS_F_CONTIGUOUS(pyArray) && - PyArray_IS_C_CONTIGUOUS(pyArray))) // no need to allocate - need_to_allocate |= false; - else - need_to_allocate |= true; + bool incompatible_layout = + !is_arr_layout_compatible_with_mat_type<MatType>(pyArray); + need_to_allocate |= incompatible_layout; if (Options != Eigen::Unaligned) // we need to check whether the memory is correctly // aligned and composed of a continuous segment @@ -365,16 +374,9 @@ struct EigenAllocator<const Eigen::Ref<const MatType, Options, Stride> > { const int Scalar_type_code = Register::getTypeCode<Scalar>(); if (pyArray_type_code != Scalar_type_code) need_to_allocate |= true; - if ((MatType::IsRowMajor && (PyArray_IS_C_CONTIGUOUS(pyArray) && - !PyArray_IS_F_CONTIGUOUS(pyArray))) || - (!MatType::IsRowMajor && (PyArray_IS_F_CONTIGUOUS(pyArray) && - !PyArray_IS_C_CONTIGUOUS(pyArray))) || - MatType::IsVectorAtCompileTime || - (PyArray_IS_F_CONTIGUOUS(pyArray) && - PyArray_IS_C_CONTIGUOUS(pyArray))) // no need to allocate - need_to_allocate |= false; - else - need_to_allocate |= true; + bool incompatible_layout = + !is_arr_layout_compatible_with_mat_type<MatType>(pyArray); + need_to_allocate |= incompatible_layout; if (Options != Eigen::Unaligned) // we need to check whether the memory is correctly // aligned and composed of a continuous segment diff --git a/include/eigenpy/pickle-vector.hpp b/include/eigenpy/pickle-vector.hpp new file mode 100644 index 0000000000000000000000000000000000000000..368bb30c307f8ac15f4a061f983010c2adb6187c --- /dev/null +++ b/include/eigenpy/pickle-vector.hpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2019-2020 CNRS INRIA +// + +#ifndef __eigenpy_utils_pickle_vector_hpp__ +#define __eigenpy_utils_pickle_vector_hpp__ + +#include <boost/python.hpp> +#include <boost/python/stl_iterator.hpp> +#include <boost/python/tuple.hpp> + +namespace eigenpy { +/// +/// \brief Create a pickle interface for the std::vector +/// +/// \tparam VecType Vector Type to pickle +/// +template <typename VecType> +struct PickleVector : boost::python::pickle_suite { + static boost::python::tuple getinitargs(const VecType&) { + return boost::python::make_tuple(); + } + + static boost::python::tuple getstate(boost::python::object op) { + return boost::python::make_tuple( + boost::python::list(boost::python::extract<const VecType&>(op)())); + } + + static void setstate(boost::python::object op, boost::python::tuple tup) { + if (boost::python::len(tup) > 0) { + VecType& o = boost::python::extract<VecType&>(op)(); + boost::python::stl_input_iterator<typename VecType::value_type> begin( + tup[0]), + end; + while (begin != end) { + o.push_back(*begin); + ++begin; + } + } + } + + static bool getstate_manages_dict() { return true; } +}; +} // namespace eigenpy + +#endif // ifndef __eigenpy_utils_pickle_vector_hpp__ diff --git a/include/eigenpy/quaternion.hpp b/include/eigenpy/quaternion.hpp index 61ee052a987fe08cf4869e33b885c5535cfd4482..88f899db18bb49c829998424a6c87dbb6507371d 100644 --- a/include/eigenpy/quaternion.hpp +++ b/include/eigenpy/quaternion.hpp @@ -294,8 +294,8 @@ class QuaternionVisitor return q; } - static Quaternion* FromTwoVectors(const Eigen::Ref<Vector3> u, - const Eigen::Ref<Vector3> v) { + static Quaternion* FromTwoVectors(const Eigen::Ref<const Vector3> u, + const Eigen::Ref<const Vector3> v) { Quaternion* q(new Quaternion); q->setFromTwoVectors(u, v); return q; @@ -308,12 +308,12 @@ class QuaternionVisitor static Quaternion* DefaultConstructor() { return new Quaternion; } - static Quaternion* FromOneVector(const Eigen::Ref<Vector4> v) { + static Quaternion* FromOneVector(const Eigen::Ref<const Vector4> v) { Quaternion* q(new Quaternion(v[3], v[0], v[1], v[2])); return q; } - static Quaternion* FromRotationMatrix(const Eigen::Ref<Matrix3> R) { + static Quaternion* FromRotationMatrix(const Eigen::Ref<const Matrix3> R) { Quaternion* q(new Quaternion(R)); return q; } diff --git a/include/eigenpy/std-map.hpp b/include/eigenpy/std-map.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b6748f5712c57a7cf209e93cb50d605c1c61b854 --- /dev/null +++ b/include/eigenpy/std-map.hpp @@ -0,0 +1,64 @@ +/// Copyright (c) 2016-2022 CNRS INRIA +/// This file was taken from Pinocchio (header +/// <pinocchio/bindings/python/utils/std-vector.hpp>) +/// + +#ifndef __eigenpy_utils_map_hpp__ +#define __eigenpy_utils_map_hpp__ + +#include <boost/python/suite/indexing/map_indexing_suite.hpp> + +namespace eigenpy { +namespace details { +template <typename Container> +struct overload_base_get_item_for_std_map + : public boost::python::def_visitor< + overload_base_get_item_for_std_map<Container> > { + typedef typename Container::value_type value_type; + typedef typename Container::value_type::second_type data_type; + typedef typename Container::key_type key_type; + typedef typename Container::key_type index_type; + + template <class Class> + void visit(Class& cl) const { + cl.def("__getitem__", &base_get_item); + } + + private: + static boost::python::object base_get_item( + boost::python::back_reference<Container&> container, PyObject* i_) { + namespace bp = ::boost::python; + + index_type idx = convert_index(container.get(), i_); + typename Container::iterator i = container.get().find(idx); + if (i == container.get().end()) { + PyErr_SetString(PyExc_KeyError, "Invalid key"); + bp::throw_error_already_set(); + } + + typename bp::to_python_indirect<data_type&, + bp::detail::make_reference_holder> + convert; + return bp::object(bp::handle<>(convert(i->second))); + } + + static index_type convert_index(Container& /*container*/, PyObject* i_) { + namespace bp = ::boost::python; + bp::extract<key_type const&> i(i_); + if (i.check()) { + return i(); + } else { + bp::extract<key_type> i(i_); + if (i.check()) return i(); + } + + PyErr_SetString(PyExc_TypeError, "Invalid index type"); + bp::throw_error_already_set(); + return index_type(); + } +}; + +} // namespace details +} // namespace eigenpy + +#endif // ifndef __eigenpy_utils_map_hpp__ diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cb6d869e0ff4d2d84f719b33fa5d6ae37c8b352d --- /dev/null +++ b/include/eigenpy/std-vector.hpp @@ -0,0 +1,458 @@ +/// Copyright (c) 2016-2022 CNRS INRIA +/// This file was taken from Pinocchio (header +/// <pinocchio/bindings/python/utils/std-vector.hpp>) +/// + +#ifndef __eigenpy_utils_std_vector_hpp__ +#define __eigenpy_utils_std_vector_hpp__ + +#include <boost/mpl/if.hpp> +#include <boost/python.hpp> +#include <boost/python/stl_iterator.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> +#include <iterator> +#include <string> +#include <vector> + +#include "eigenpy/config.hpp" +#include "eigenpy/copyable.hpp" +#include "eigenpy/eigen-to-python.hpp" +#include "eigenpy/pickle-vector.hpp" +#include "eigenpy/registration.hpp" + +namespace eigenpy { +// Forward declaration +template <typename vector_type, bool NoProxy = false> +struct StdContainerFromPythonList; + +namespace details { + +/// \brief Check if a PyObject can be converted to an std::vector<T>. +template <typename T> +bool from_python_list(PyObject *obj_ptr, T *) { + namespace bp = ::boost::python; + + // Check if it is a list + if (!PyList_Check(obj_ptr)) return false; + + // Retrieve the underlying list + bp::object bp_obj(bp::handle<>(bp::borrowed(obj_ptr))); + bp::list bp_list(bp_obj); + bp::ssize_t list_size = bp::len(bp_list); + + // Check if all the elements contained in the current vector is of type T + for (bp::ssize_t k = 0; k < list_size; ++k) { + bp::extract<T> elt(bp_list[k]); + if (!elt.check()) return false; + } + + return true; +} + +template <typename vector_type, bool NoProxy> +struct build_list { + static ::boost::python::list run(vector_type &vec) { + namespace bp = ::boost::python; + + bp::list bp_list; + for (size_t k = 0; k < vec.size(); ++k) { + bp_list.append(boost::ref(vec[k])); + } + return bp_list; + } +}; + +template <typename vector_type> +struct build_list<vector_type, true> { + static ::boost::python::list run(vector_type &vec) { + namespace bp = ::boost::python; + + typedef bp::iterator<vector_type> iterator; + return bp::list(iterator()(vec)); + } +}; + +/// \brief Change the behaviour of indexing (method __getitem__ in Python). +/// This is suitable for container of Eigen matrix objects if you want to mutate +/// them. +template <typename Container> +struct overload_base_get_item_for_std_vector + : public boost::python::def_visitor< + overload_base_get_item_for_std_vector<Container> > { + typedef typename Container::value_type value_type; + typedef typename Container::value_type data_type; + typedef size_t index_type; + + template <class Class> + void visit(Class &cl) const { + cl.def("__getitem__", &base_get_item); + } + + private: + static boost::python::object base_get_item( + boost::python::back_reference<Container &> container, PyObject *i_) { + namespace bp = ::boost::python; + + index_type idx = convert_index(container.get(), i_); + typename Container::iterator i = container.get().begin(); + std::advance(i, idx); + if (i == container.get().end()) { + PyErr_SetString(PyExc_KeyError, "Invalid index"); + bp::throw_error_already_set(); + } + + typename bp::to_python_indirect<data_type &, + bp::detail::make_reference_holder> + convert; + return bp::object(bp::handle<>(convert(*i))); + } + + static index_type convert_index(Container &container, PyObject *i_) { + namespace bp = boost::python; + bp::extract<long> i(i_); + if (i.check()) { + long index = i(); + if (index < 0) index += (long)container.size(); + if (index >= long(container.size()) || index < 0) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + bp::throw_error_already_set(); + } + return (index_type)index; + } + + PyErr_SetString(PyExc_TypeError, "Invalid index type"); + bp::throw_error_already_set(); + return index_type(); + } +}; +} // namespace details +} // namespace eigenpy + +namespace boost { +namespace python { + +template <typename MatrixType> +struct extract_to_eigen_ref + : converter::extract_rvalue<Eigen::Ref<MatrixType> > { + typedef Eigen::Ref<MatrixType> RefType; + + protected: + typedef converter::extract_rvalue<RefType> base; + + public: + typedef RefType result_type; + + operator result_type() const { return (*this)(); } + + extract_to_eigen_ref(PyObject *o) : base(o) {} + extract_to_eigen_ref(api::object const &o) : base(o.ptr()) {} +}; + +/// \brief Specialization of the boost::python::extract struct for references to +/// Eigen matrix objects. +template <typename Scalar, int Rows, int Cols, int Options, int MaxRows, + int MaxCols> +struct extract<Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> &> + : extract_to_eigen_ref< + Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> > { + typedef Eigen::Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> + MatrixType; + typedef extract_to_eigen_ref<MatrixType> base; + extract(PyObject *o) : base(o) {} + extract(api::object const &o) : base(o.ptr()) {} +}; + +template <typename Derived> +struct extract<Eigen::MatrixBase<Derived> &> + : extract_to_eigen_ref<Eigen::MatrixBase<Derived> > { + typedef Eigen::MatrixBase<Derived> MatrixType; + typedef extract_to_eigen_ref<MatrixType> base; + extract(PyObject *o) : base(o) {} + extract(api::object const &o) : base(o.ptr()) {} +}; + +template <typename Derived> +struct extract<Eigen::RefBase<Derived> &> + : extract_to_eigen_ref<Eigen::RefBase<Derived> > { + typedef Eigen::RefBase<Derived> MatrixType; + typedef extract_to_eigen_ref<MatrixType> base; + extract(PyObject *o) : base(o) {} + extract(api::object const &o) : base(o.ptr()) {} +}; + +namespace converter { + +template <typename Type, class Allocator> +struct reference_arg_from_python<std::vector<Type, Allocator> &> + : arg_lvalue_from_python_base { + typedef std::vector<Type, Allocator> vector_type; + typedef vector_type &ref_vector_type; + typedef ref_vector_type result_type; + typedef extract<Type &> extract_type; + + reference_arg_from_python(PyObject *py_obj) + : arg_lvalue_from_python_base(converter::get_lvalue_from_python( + py_obj, registered<vector_type>::converters)), + m_data(NULL), + m_source(py_obj), + vec_ptr(NULL) { + if (result() != 0) // we have found a lvalue converter + return; + + // Check if py_obj is a py_list, which can then be converted to an + // std::vector + bool is_convertible = + ::eigenpy::details::from_python_list(py_obj, (Type *)(0)); + if (!is_convertible) return; + + typedef ::eigenpy::StdContainerFromPythonList<vector_type> Constructor; + Constructor::construct(py_obj, &m_data.stage1); + + void *&m_result = const_cast<void *&>(result()); + m_result = m_data.stage1.convertible; + vec_ptr = reinterpret_cast<vector_type *>(m_data.storage.bytes); + } + + result_type operator()() const { + return ::boost::python::detail::void_ptr_to_reference(result(), + (result_type(*)())0); + } + + ~reference_arg_from_python() { + if (m_data.stage1.convertible == m_data.storage.bytes) { + // Copy back the reference + const vector_type &vec = *vec_ptr; + list bp_list(handle<>(borrowed(m_source))); + for (size_t i = 0; i < vec.size(); ++i) { + typename extract_type::result_type elt = extract_type(bp_list[i]); + elt = vec[i]; + } + } + } + + private: + rvalue_from_python_data<ref_vector_type> m_data; + PyObject *m_source; + vector_type *vec_ptr; +}; + +} // namespace converter +} // namespace python +} // namespace boost + +namespace eigenpy { + +/// +/// \brief Register the conversion from a Python list to a std::vector +/// +/// \tparam vector_type A std container (e.g. std::vector or std::list) +/// +template <typename vector_type, bool NoProxy> +struct StdContainerFromPythonList { + typedef typename vector_type::value_type T; + typedef typename vector_type::allocator_type Allocator; + + /// \brief Check if obj_ptr can be converted + static void *convertible(PyObject *obj_ptr) { + namespace bp = boost::python; + + // Check if it is a list + if (!PyList_Check(obj_ptr)) return 0; + + // Retrieve the underlying list + bp::object bp_obj(bp::handle<>(bp::borrowed(obj_ptr))); + bp::list bp_list(bp_obj); + bp::ssize_t list_size = bp::len(bp_list); + + // Check if all the elements contained in the current vector is of type T + for (bp::ssize_t k = 0; k < list_size; ++k) { + bp::extract<T> elt(bp_list[k]); + if (!elt.check()) return 0; + } + + return obj_ptr; + } + + /// \brief Allocate the std::vector and fill it with the element contained in + /// the list + static void construct( + PyObject *obj_ptr, + boost::python::converter::rvalue_from_python_stage1_data *memory) { + namespace bp = boost::python; + + // Extract the list + bp::object bp_obj(bp::handle<>(bp::borrowed(obj_ptr))); + bp::list bp_list(bp_obj); + + void *storage = + reinterpret_cast< + bp::converter::rvalue_from_python_storage<vector_type> *>( + reinterpret_cast<void *>(memory)) + ->storage.bytes; + + typedef bp::stl_input_iterator<T> iterator; + + // Build the std::vector + new (storage) vector_type(iterator(bp_list), iterator()); + + // Validate the construction + memory->convertible = storage; + } + + static void register_converter() { + ::boost::python::converter::registry::push_back( + &convertible, &construct, ::boost::python::type_id<vector_type>()); + } + + static ::boost::python::list tolist(vector_type &self) { + return details::build_list<vector_type, NoProxy>::run(self); + } +}; + +namespace internal { + +template <typename T> +struct has_operator_equal + : boost::mpl::if_<typename boost::is_base_of<Eigen::EigenBase<T>, T>::type, + has_operator_equal<Eigen::EigenBase<T> >, + boost::true_type>::type {}; + +template <typename T, class A> +struct has_operator_equal<std::vector<T, A> > : has_operator_equal<T> {}; + +template <> +struct has_operator_equal<bool> : boost::true_type {}; + +template <typename EigenObject> +struct has_operator_equal<Eigen::EigenBase<EigenObject> > + : has_operator_equal<typename EigenObject::Scalar> {}; + +template <typename T, bool has_operator_equal_value = boost::is_base_of< + boost::true_type, has_operator_equal<T> >::value> +struct contains_algo; + +template <typename T> +struct contains_algo<T, true> { + template <class Container, typename key_type> + static bool run(Container &container, key_type const &key) { + return std::find(container.begin(), container.end(), key) != + container.end(); + } +}; + +template <typename T> +struct contains_algo<T, false> { + template <class Container, typename key_type> + static bool run(Container &container, key_type const &key) { + for (size_t k = 0; k < container.size(); ++k) { + if (&container[k] == &key) return true; + } + return false; + } +}; + +template <class Container, bool NoProxy> +struct contains_vector_derived_policies + : public ::boost::python::vector_indexing_suite< + Container, NoProxy, + contains_vector_derived_policies<Container, NoProxy> > { + typedef typename Container::value_type key_type; + + static bool contains(Container &container, key_type const &key) { + return contains_algo<key_type>::run(container, key); + } +}; +} // namespace internal + +struct EmptyPythonVisitor + : public ::boost::python::def_visitor<EmptyPythonVisitor> { + template <class classT> + void visit(classT &) const {} +}; + +/// +/// \brief Expose an std::vector from a type given as template argument. +/// +/// \tparam T Type to expose as std::vector<T>. +/// \tparam Allocator Type for the Allocator in std::vector<T,Allocator>. +/// \tparam NoProxy When set to false, the elements will be copied when returned +/// to Python. \tparam EnableFromPythonListConverter Enables the conversion from +/// a Python list to a std::vector<T,Allocator> +/// +/// \sa StdAlignedVectorPythonVisitor +/// +template <class vector_type, bool NoProxy = false, + bool EnableFromPythonListConverter = true> +struct StdVectorPythonVisitor + : public ::boost::python::vector_indexing_suite< + vector_type, NoProxy, + internal::contains_vector_derived_policies<vector_type, NoProxy> >, + public StdContainerFromPythonList<vector_type, NoProxy> { + typedef typename vector_type::value_type value_type; + typedef typename vector_type::allocator_type allocator_type; + typedef StdContainerFromPythonList<vector_type, NoProxy> + FromPythonListConverter; + + static void expose(const std::string &class_name, + const std::string &doc_string = "") { + expose(class_name, doc_string, EmptyPythonVisitor()); + } + + template <typename VisitorDerived> + static void expose( + const std::string &class_name, + const boost::python::def_visitor<VisitorDerived> &visitor) { + expose(class_name, "", visitor); + } + + template <typename VisitorDerived> + static void expose( + const std::string &class_name, const std::string &doc_string, + const boost::python::def_visitor<VisitorDerived> &visitor) { + namespace bp = boost::python; + + if (!register_symbolic_link_to_registered_type<vector_type>()) { + bp::class_<vector_type> cl(class_name.c_str(), doc_string.c_str()); + cl.def(StdVectorPythonVisitor()) + + .def(bp::init<size_t, const value_type &>( + bp::args("self", "size", "value"), + "Constructor from a given size and a given value.")) + .def(bp::init<const vector_type &>(bp::args("self", "other"), + "Copy constructor")) + + .def("tolist", &FromPythonListConverter::tolist, bp::arg("self"), + "Returns the std::vector as a Python list.") + .def(visitor) + .def("reserve", &vector_type::reserve, + (bp::arg("self"), bp::arg("new_cap")), + "Increase the capacity of the vector to a value that's greater " + "or equal to new_cap.") + .def_pickle(PickleVector<vector_type>()) + .def(CopyableVisitor<vector_type>()); + + // Register conversion + if (EnableFromPythonListConverter) + FromPythonListConverter::register_converter(); + } + } +}; + +/** + * Expose std::vector for given matrix or vector sizes. + */ +void EIGENPY_DLLAPI exposeStdVector(); + +template <typename MatType> +void exposeStdVectorEigenSpecificType(const char *name) { + typedef std::vector<MatType> VecMatType; + std::string full_name = "StdVec_"; + full_name += name; + StdVectorPythonVisitor<VecMatType, false>::expose( + full_name.c_str(), + details::overload_base_get_item_for_std_vector<VecMatType>()); +} + +} // namespace eigenpy + +#endif // ifndef __eigenpy_utils_std_vector_hpp__ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 10f7d755e6e4a0ecb0ce23e13b166890f7ea4a22..5de5574b2d79e2611baa5963fae1c5b32d0ef504 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -35,6 +35,12 @@ set_target_properties( PROPERTIES PREFIX "" SUFFIX ${PYTHON_EXT_SUFFIX} LIBRARY_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}" + LIBRARY_OUTPUT_DIRECTORY_<CONFIG> + "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}" + RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}" + RUNTIME_OUTPUT_DIRECTORY_<CONFIG> "${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}") if(UNIX AND NOT APPLE) diff --git a/python/main.cpp b/python/main.cpp index b36c56e06becb4ca2d85479c3dbe8b8b32ab5dab..9c5b49a82b7ca9066f1aedf41913374d3d5f4f4b 100644 --- a/python/main.cpp +++ b/python/main.cpp @@ -11,6 +11,7 @@ #include "eigenpy/geometry.hpp" #include "eigenpy/solvers/preconditioners.hpp" #include "eigenpy/solvers/solvers.hpp" +#include "eigenpy/std-vector.hpp" #include "eigenpy/utils/is-approx.hpp" #include "eigenpy/version.hpp" @@ -30,6 +31,7 @@ BOOST_PYTHON_MODULE(eigenpy_pywrap) { exposeAngleAxis(); exposeQuaternion(); exposeGeometryConversion(); + exposeStdVector(); exposeComputationInfo(); diff --git a/src/std-vector.cpp b/src/std-vector.cpp new file mode 100644 index 0000000000000000000000000000000000000000..45cbcd3150549eab2cece1e12037e2121add6505 --- /dev/null +++ b/src/std-vector.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2022, CNRS + * Copyright 2022, INRIA + */ + +#include "eigenpy/std-vector.hpp" + +namespace eigenpy { + +void exposeStdVector() { + exposeStdVectorEigenSpecificType<Eigen::MatrixXd>("MatrixXd"); + exposeStdVectorEigenSpecificType<Eigen::VectorXd>("VectorXd"); + + exposeStdVectorEigenSpecificType<Eigen::MatrixXi>("MatrixXi"); + exposeStdVectorEigenSpecificType<Eigen::VectorXi>("VectorXi"); +} + +} // namespace eigenpy diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 55d6b569944d46878c5c08da0a81a0c00b89f6bf..7ef9001d42e311edf83ebfaa43fc22964b5d86f3 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -33,6 +33,7 @@ if(NOT ${EIGEN3_VERSION} VERSION_LESS "3.2.0") add_lib_unit_test(eigen_ref) endif() add_lib_unit_test(user_type) +add_lib_unit_test(std_vector) add_python_unit_test("py-matrix" "unittest/python/test_matrix.py" "unittest") add_python_unit_test("py-geometry" "unittest/python/test_geometry.py" @@ -78,3 +79,7 @@ if(NOT WIN32) "python;unittest") set_tests_properties("py-MINRES" PROPERTIES DEPENDS ${PYWRAP}) endif(NOT WIN32) + +add_python_unit_test("py-std-vector" "unittest/python/test_std_vector.py" + "python;unittest") +set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP}) diff --git a/unittest/eigen_ref.cpp b/unittest/eigen_ref.cpp index 78896b9a376ee15228beca98bc7e36ef776d6959..9c334d10bbb748ed0bd21f94c6580f6787b3dd3e 100644 --- a/unittest/eigen_ref.cpp +++ b/unittest/eigen_ref.cpp @@ -6,7 +6,6 @@ #include <iostream> #include "eigenpy/eigenpy.hpp" -// include main header first #include "eigenpy/eigen-from-python.hpp" using namespace Eigen; @@ -15,7 +14,7 @@ using namespace eigenpy; template <typename MatType> void printMatrix(const Eigen::Ref<const MatType> mat) { if (MatType::IsVectorAtCompileTime) std::cout << "isVector" << std::endl; - std::cout << "size: cols " << mat.cols() << " rows " << mat.rows() + std::cout << "input size: cols " << mat.cols() << " rows " << mat.rows() << std::endl; std::cout << mat << std::endl; } @@ -58,20 +57,22 @@ void fill(Eigen::Ref<MatType> mat, const typename MatType::Scalar& value) { mat.fill(value); } +/// Get ref to a static matrix of size ( @p rows, @p cols ) template <typename MatType> -Eigen::Ref<MatType> asRef(const int rows, const int cols) { +Eigen::Ref<MatType> getRefToStatic(const int rows, const int cols) { static MatType mat(rows, cols); - std::cout << "mat:\n" << mat << std::endl; + std::cout << "create ref to matrix of size (" << rows << "," << cols << ")\n"; return mat; } template <typename MatType> Eigen::Ref<MatType> asRef(Eigen::Ref<MatType> mat) { + std::cout << "create Ref to input mutable Ref\n"; return Eigen::Ref<MatType>(mat); } template <typename MatType> -const Eigen::Ref<const MatType> asConstRef(Eigen::Ref<MatType> mat) { +const Eigen::Ref<const MatType> asConstRef(Eigen::Ref<const MatType> mat) { return Eigen::Ref<const MatType>(mat); } @@ -82,8 +83,8 @@ struct modify_block { virtual void call(Eigen::Ref<MatrixXd> mat) = 0; }; -struct modify_wrap : modify_block, bp::wrapper<modify_block> { - modify_wrap() : modify_block() {} +struct modify_block_wrap : modify_block, bp::wrapper<modify_block> { + modify_block_wrap() : modify_block() {} void call(Eigen::Ref<MatrixXd> mat) { this->get_override("call")(mat); } }; @@ -112,20 +113,18 @@ BOOST_PYTHON_MODULE(eigen_ref) { bp::def("fillVec", fill<VectorXd>); bp::def("fill", fill<MatrixXd>); - bp::def("asRef", - (Eigen::Ref<MatrixXd>(*)(const int, const int))asRef<MatrixXd>); - bp::def("asRef", - (Eigen::Ref<MatrixXd>(*)(Eigen::Ref<MatrixXd>))asRef<MatrixXd>); - bp::def("asConstRef", (const Eigen::Ref<const MatrixXd> (*)( - Eigen::Ref<MatrixXd>))asConstRef<MatrixXd>); + bp::def("getRefToStatic", getRefToStatic<MatrixXd>); + bp::def("asRef", asRef<MatrixXd>); + bp::def("asConstRef", asConstRef<MatrixXd>); bp::def("getBlock", &getBlock<MatrixXd>); bp::def("editBlock", &editBlock<MatrixXd>); - bp::class_<modify_wrap, boost::noncopyable>("modify_block", bp::init<>()) + bp::class_<modify_block_wrap, boost::noncopyable>("modify_block", + bp::init<>()) .def_readonly("J", &modify_block::J) .def("modify", &modify_block::modify) - .def("call", bp::pure_virtual(&modify_wrap::call)); + .def("call", bp::pure_virtual(&modify_block_wrap::call)); bp::class_<has_ref_member, boost::noncopyable>("has_ref_member", bp::init<>()) .def_readonly("J", &has_ref_member::J) diff --git a/unittest/python/test_eigen_ref.py b/unittest/python/test_eigen_ref.py index 0802643624c8bec22d5da501a0056146206fca8a..fe43e501fd28abc13c7a4fab257f7ea7ccab1faa 100644 --- a/unittest/python/test_eigen_ref.py +++ b/unittest/python/test_eigen_ref.py @@ -1,6 +1,7 @@ import numpy as np from eigen_ref import ( printMatrix, + getRefToStatic, asRef, asConstRef, fill, @@ -11,61 +12,79 @@ from eigen_ref import ( ) -def test(mat): +def test_fill_print(mat): + print("print matrix:") printMatrix(mat) + print("calling fill():") fill(mat, 1.0) - printMatrix(mat) assert np.array_equal(mat, np.full(mat.shape, 1.0)) - A_ref = asRef(mat.shape[0], mat.shape[1]) + print("fill a slice") + mat[:, :] = 0.0 + fill(mat[:3, :2], 1.0) + printMatrix(mat[:3, :2]) + assert np.array_equal(mat[:3, :2], np.ones((3, 2))) + + +def test_create_ref_to_static(mat): + # create ref to static: + print() + print("[asRef(int, int)]") + A_ref = getRefToStatic(mat.shape[0], mat.shape[1]) A_ref.fill(1.0) - A_ref2 = asRef(mat.shape[0], mat.shape[1]) + A_ref[0, 1] = -1.0 + print("make second reference:") + A_ref2 = getRefToStatic(mat.shape[0], mat.shape[1]) + print(A_ref2) assert np.array_equal(A_ref, A_ref2) A_ref2.fill(0) assert np.array_equal(A_ref, A_ref2) - ref = asRef(mat) - assert np.all(ref == mat) - - const_ref = asConstRef(mat) - assert np.all(const_ref == mat) - mat.fill(0.0) - fill(mat[:3, :2], 1.0) +def test_create_ref(mat): + print("[asRef(mat)]") + ref = asRef(mat) + assert np.array_equal(ref, mat), "ref=\n{}\nmat=\n{}".format(ref, mat) + assert not (ref.flags.owndata) + assert ref.flags.writeable - assert np.all(mat[:3, :2] == np.ones((3, 2))) - mat.fill(0.0) - fill(mat[:2, :3], 1.0) +def test_create_const_ref(mat): + print("[asConstRef]") + const_ref = asConstRef(mat) + assert np.array_equal(const_ref, mat), "ref=\n{}\nmat=\n{}".format(const_ref, mat) + assert not (const_ref.flags.writeable) + assert not (const_ref.flags.owndata) - assert np.all(mat[:2, :3] == np.ones((2, 3))) +def test_edit_block(rows, cols): + print("set mat data to arange()") mat.fill(0.0) mat[:, :] = np.arange(rows * cols).reshape(rows, cols) - printMatrix(mat) mat0 = mat.copy() - mat_as_C_order = np.array(mat, order="F") for i, rowsize, colsize in ([0, 3, 2], [1, 1, 2], [0, 3, 1]): print("taking block [{}:{}, {}:{}]".format(i, rowsize + i, 0, colsize)) - B = getBlock(mat_as_C_order, i, 0, rowsize, colsize) - lhs = mat_as_C_order[i : rowsize + i, :colsize] - print("should be:\n{}\ngot:\n{}".format(lhs, B)) - assert np.array_equal(lhs, B.reshape(rowsize, colsize)) + B = getBlock(mat, i, 0, rowsize, colsize) + B = B.reshape(rowsize, colsize) + lhs = mat[i : rowsize + i, :colsize] + assert np.array_equal(lhs, B), "got lhs\n{}\nrhs B=\n{}".format(lhs, B) B[:] = 1.0 rhs = np.ones((rowsize, colsize)) - assert np.array_equal(mat_as_C_order[i : rowsize + i, :colsize], rhs) + assert np.array_equal(mat[i : rowsize + i, :colsize], rhs) - mat_as_C_order[:, :] = mat0 + mat[:, :] = mat0 - mat_copy = mat_as_C_order.copy() - editBlock(mat_as_C_order, 0, 0, 3, 2) + mat.fill(0.0) + mat_copy = mat.copy() + print("[editBlock]") + editBlock(mat, 0, 0, 3, 2) mat_copy[:3, :2] = np.arange(6).reshape(3, 2) - assert np.array_equal(mat_as_C_order, mat_copy) + assert np.array_equal(mat, mat_copy) class ModifyBlockImpl(modify_block): def __init__(self): @@ -91,9 +110,26 @@ def test(mat): assert np.array_equal(hasref.J, J_true) -rows = 10 -cols = 30 +def do_test(mat): + test_fill_print(mat) + test_create_ref_to_static(mat) + test_create_const_ref(mat) + test_create_ref(mat) + test_edit_block(rows, cols) + print("=" * 10) + + +if __name__ == "__main__": + rows = 8 + cols = 10 -mat = np.ones((rows, cols), order="F") + mat = np.ones((rows, cols), order="F") + mat[0, 0] = 0 + mat[1:5, 1:5] = 6 + do_test(mat) -test(mat) + # mat2 = np.ones((rows, cols)) + # mat2[:2, :5] = 0. + # mat2[2:4, 1:4] = 2 + # mat2[:, -1] = 3 + # do_test(mat2) diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py new file mode 100644 index 0000000000000000000000000000000000000000..52fd4c0d3cb623d178831f60f12e0551c1147959 --- /dev/null +++ b/unittest/python/test_std_vector.py @@ -0,0 +1,85 @@ +import numpy as np +import eigenpy +import inspect +import pprint +import std_vector +from std_vector import printVectorOfMatrix, printVectorOf3x3, copyStdVector + +np.random.seed(0) + +l1 = [np.random.randn(3), np.random.randn(2)] +l2 = eigenpy.StdVec_VectorXd(l1) +l3 = [np.random.randn(2, 2), np.random.randn(3, 1)] +l3.append(np.asfortranarray(np.eye(2))) +l3.append(np.eye(2)) +l4 = [np.random.randn(3, 3).T for _ in range(3)] +l4[-1] = l4[-1].T + + +def checkAllValues(li1, li2): + assert len(li1) == len(li2) + n = len(li1) + for i in range(n): + assert np.array_equal(li1[i], li2[i]) + + +checkAllValues(l1, l2) +checkAllValues(l1, copyStdVector(l1)) + +l2[0][:2] = 0.0 +assert np.allclose(l2[0][:2], 0.0) + +print("l1") +printVectorOfMatrix(l1) +print("l2") +printVectorOfMatrix(l2) +print("l3") +printVectorOfMatrix(l3) + + +l4_copy = copyStdVector(l4) +assert isinstance(l4_copy, eigenpy.StdVec_MatrixXd) + +assert "StdVec_Mat3d" in printVectorOf3x3.__doc__ +printVectorOf3x3(l4) + +l4_copy2 = std_vector.copyStdVec_3x3(l4) +assert isinstance(l4_copy2, std_vector.StdVec_Mat3d) + + +def checkZero(l): + for x in l: + assert np.allclose(x, 0.0), "x = {}".format(x) + + +print("Check setZero() works:") +print("l1:") +std_vector.setZero(l1) +print(l1) +checkZero(l1) +print("-----------------") + +print("l2:") +l2_py = l2.tolist() +std_vector.setZero(l2_py) +pprint.pprint(l2_py) +checkZero(l2_py) +print("-----------------") + +l3_copy = copyStdVector(l3) +print("l3_std:") +std_vector.setZero(l3_copy) +pprint.pprint(list(l3_copy)) +checkZero(l3_copy) +print("-----------------") + +# print("l3_python:") +# vector.setZero(l3) +# pprint.pprint(list(l3)) +# checkZero(l3) +# print("-----------------") + +# print("l4:") +# vector.setZero(l4) +# pprint.pprint(list(l4)) +# checkZero(l4) diff --git a/unittest/std_vector.cpp b/unittest/std_vector.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a3679917a61916f761d0202755f935192cd2a442 --- /dev/null +++ b/unittest/std_vector.cpp @@ -0,0 +1,50 @@ +/// @file +/// @copyright Copyright 2022, CNRS +/// @copyright Copyright 2022, INRIA +#include <ostream> + +#include "eigenpy/eigenpy.hpp" +#include "eigenpy/eigen-from-python.hpp" +#include "eigenpy/std-vector.hpp" + +template <typename MatType> +void printVectorOfMatrix(const std::vector<MatType> &Ms) { + const std::size_t n = Ms.size(); + for (std::size_t i = 0; i < n; i++) { + std::cout << "el[" << i << "] =\n" << Ms[i] << '\n'; + } +} + +template <typename MatType> +std::vector<MatType> copy(const std::vector<MatType> &Ms) { + std::vector<MatType> out = Ms; + return out; +} + +template <typename MatType> +void setZero(std::vector<MatType> &Ms) { + for (std::size_t i = 0; i < Ms.size(); i++) { + Ms[i].setZero(); + } +} + +BOOST_PYTHON_MODULE(std_vector) { + namespace bp = boost::python; + using namespace eigenpy; + + enableEigenPy(); + + bp::def("printVectorOfMatrix", printVectorOfMatrix<Eigen::VectorXd>); + bp::def("printVectorOfMatrix", printVectorOfMatrix<Eigen::MatrixXd>); + + bp::def("copyStdVector", copy<Eigen::MatrixXd>); + bp::def("copyStdVector", copy<Eigen::VectorXd>); + + exposeStdVectorEigenSpecificType<Eigen::Matrix3d>("Mat3d"); + bp::def("printVectorOf3x3", printVectorOfMatrix<Eigen::Matrix3d>); + bp::def("copyStdVec_3x3", copy<Eigen::Matrix3d>, bp::args("mats")); + + typedef Eigen::Ref<Eigen::MatrixXd> RefXd; + StdVectorPythonVisitor<std::vector<RefXd>, true>::expose("StdVec_MatRef"); + bp::def("setZero", setZero<Eigen::MatrixXd>, "Sets the coeffs to 0."); +}