From 727fad660afee88ebf3b5ba530936906518f33f1 Mon Sep 17 00:00:00 2001 From: Joris Vaillant Date: Tue, 24 Oct 2023 18:14:15 +0200 Subject: [PATCH 1/4] [WIP] core: add methods to already defined vector type --- include/eigenpy/registration.hpp | 20 ++++ include/eigenpy/registration_class.hpp | 129 +++++++++++++++++++++++++ include/eigenpy/std-vector.hpp | 65 ++++++++----- unittest/python/test_std_vector.py | 10 ++ unittest/std_vector.cpp | 8 ++ 5 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 include/eigenpy/registration_class.hpp diff --git a/include/eigenpy/registration.hpp b/include/eigenpy/registration.hpp index f7f77b69a..7644f2916 100644 --- a/include/eigenpy/registration.hpp +++ b/include/eigenpy/registration.hpp @@ -7,6 +7,7 @@ #define __eigenpy_registration_hpp__ #include "eigenpy/fwd.hpp" +#include "eigenpy/registration_class.hpp" namespace eigenpy { @@ -50,6 +51,25 @@ inline bool register_symbolic_link_to_registered_type() { return false; } + +/// Same as \see register_symbolic_link_to_registered_type() but apply \p +/// visitor on \tparam T if it already exists +template +inline bool register_symbolic_link_to_registered_type(const Visitor& visitor) { + if (eigenpy::check_registration()) { + const bp::type_info info = bp::type_id(); + const bp::converter::registration* reg = + bp::converter::registry::query(info); + bp::handle<> class_obj(reg->get_class_object()); + bp::object object(class_obj); + bp::scope().attr(reg->get_class_object()->tp_name) = object; + registration_class cl(object); + cl.def(visitor); + return true; + } + + return false; +} } // namespace eigenpy #endif // ifndef __eigenpy_registration_hpp__ diff --git a/include/eigenpy/registration_class.hpp b/include/eigenpy/registration_class.hpp new file mode 100644 index 000000000..ebe72d7f7 --- /dev/null +++ b/include/eigenpy/registration_class.hpp @@ -0,0 +1,129 @@ +/* + * Copyright 2023, INRIA + */ + +#ifndef __eigenpy_registration_class_hpp__ +#define __eigenpy_registration_class_hpp__ + +#include + +#include "eigenpy/fwd.hpp" + +namespace eigenpy { + +/*! Copy of the \see boost::python::class_ + * This class allow to add methods to an existing class without registering it + * again. + **/ +template +class registration_class { + public: + using self = registration_class; + + /// \p object Hold the namespace of the class that will be modified + registration_class(bp::object object) : m_object(object) {} + + /// \see boost::python::class_::def(bp::def_visitor const& visitor) + template + self& def(Visitor const& visitor) { + visitor.visit(*this); + return *this; + } + + /// \see boost::python::class_::def(char const* name, F f) + template + self& def(char const* name, F f) { + def_impl(bp::detail::unwrap_wrapper((W*)0), name, f, + bp::detail::def_helper(0), &f); + return *this; + } + + /// \see boost::python::class_::def(char const* name, A1 a1, A2 const& a2) + template + self& def(char const* name, A1 a1, A2 const& a2) { + def_maybe_overloads(name, a1, a2, &a2); + return *this; + } + + /// \see boost::python::class_::def(char const* name, Fn fn, A1 const& a1, A2 + /// const& a2) + template + self& def(char const* name, Fn fn, A1 const& a1, A2 const& a2) { + def_impl(bp::detail::unwrap_wrapper((W*)0), name, fn, + bp::detail::def_helper(a1, a2), &fn); + + return *this; + } + + /// \see boost::python::class_::def(char const* name, Fn fn, A1 const& a1, A2 + /// const& a2, A3 const& a3) + template + self& def(char const* name, Fn fn, A1 const& a1, A2 const& a2, A3 const& a3) { + def_impl(bp::detail::unwrap_wrapper((W*)0), name, fn, + bp::detail::def_helper(a1, a2, a3), &fn); + + return *this; + } + + private: + /// \see boost::python::class_::def_impl(T*, char const* name, Fn fn, Helper + /// const& helper, ...) + template + inline void def_impl(T*, char const* name, Fn fn, Helper const& helper, ...) { + bp::objects::add_to_namespace( + m_object, name, + make_function(fn, helper.policies(), helper.keywords(), + bp::detail::get_signature(fn, (T*)0)), + helper.doc()); + + def_default(name, fn, helper, + boost::mpl::bool_()); + } + + /// \see boost::python::class_::def_default(char const* name, Fn, Helper + /// const& helper, boost::mpl::bool_) + template + inline void def_default(char const* name, Fn, Helper const& helper, + boost::mpl::bool_) { + bp::detail::error::virtual_function_default< + W, Fn>::must_be_derived_class_member(helper.default_implementation()); + + bp::objects::add_to_namespace( + m_object, name, + make_function(helper.default_implementation(), helper.policies(), + helper.keywords())); + } + + /// \see boost::python::class_::def_default(char const*, Fn, Helper const&, + /// boost::mpl::bool_) + template + inline void def_default(char const*, Fn, Helper const&, + boost::mpl::bool_) {} + + /// \see boost::python::class_::def_maybe_overloads(char const* name, SigT + /// sig,OverloadsT const& overloads,bp::detail::overloads_base const*) + template + void def_maybe_overloads(char const* name, SigT sig, + OverloadsT const& overloads, + bp::detail::overloads_base const*) + + { + bp::detail::define_with_defaults(name, overloads, *this, + bp::detail::get_signature(sig)); + } + + /// \see boost::python::class_::def_maybe_overloads(char const* name, Fn fn, + /// A1 const& a1, ...) + template + void def_maybe_overloads(char const* name, Fn fn, A1 const& a1, ...) { + def_impl(bp::detail::unwrap_wrapper((W*)0), name, fn, + bp::detail::def_helper(a1), &fn); + } + + private: + bp::object m_object; +}; + +} // namespace eigenpy + +#endif // ifndef __eigenpy_registration_class_hpp__ diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index 8d732158a..e5a1894a7 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -360,14 +360,40 @@ struct EmptyPythonVisitor void visit(classT &) const {} }; +/// \brief Add standard method to a std::vector. +template +struct add_std_method_to_std_vector + : public boost::python::def_visitor< + add_std_method_to_std_vector > { + typedef typename Container::value_type value_type; + typedef typename Container::value_type data_type; + typedef size_t index_type; + + typedef StdContainerFromPythonList + FromPythonListConverter; + + template + void visit(Class &cl) const { + details::overload_base_get_item_for_std_vector get_item_visitor; + cl.def("tolist", &FromPythonListConverter::tolist, bp::arg("self"), + "Returns the std::vector as a Python list.") + .def(get_item_visitor) + .def("reserve", &Container::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(CopyableVisitor()); + } +}; + /// /// \brief Expose an std::vector from a type given as template argument. /// /// \tparam T Type to expose as std::vector. /// \tparam Allocator Type for the Allocator in std::vector. -/// \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 +/// \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 /// /// \sa StdAlignedVectorPythonVisitor /// @@ -388,18 +414,15 @@ struct StdVectorPythonVisitor expose(class_name, doc_string, EmptyPythonVisitor()); } - template - static void expose( - const std::string &class_name, - const boost::python::def_visitor &visitor) { + template + static void expose(const std::string &class_name, const Visitor &visitor) { expose(class_name, "", visitor); } - template - static void expose( - const std::string &class_name, const std::string &doc_string, - const boost::python::def_visitor &visitor) { - if (!register_symbolic_link_to_registered_type()) { + template + static void expose(const std::string &class_name, + const std::string &doc_string, const Visitor &visitor) { + if (!register_symbolic_link_to_registered_type(visitor)) { bp::class_ cl(class_name.c_str(), doc_string.c_str()); cl.def(StdVectorPythonVisitor()) @@ -409,20 +432,11 @@ struct StdVectorPythonVisitor .def(bp::init(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()) - .def(CopyableVisitor()); - - // Register conversion - if (EnableFromPythonListConverter) - FromPythonListConverter::register_converter(); + .def_pickle(PickleVector()); } + // Register conversion + FromPythonListConverter::register_converter(); } }; @@ -437,8 +451,7 @@ void exposeStdVectorEigenSpecificType(const char *name) { std::string full_name = "StdVec_"; full_name += name; StdVectorPythonVisitor::expose( - full_name.c_str(), - details::overload_base_get_item_for_std_vector()); + full_name.c_str(), add_std_method_to_std_vector()); } } // namespace eigenpy diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py index 8cd6ab768..532b7df80 100644 --- a/unittest/python/test_std_vector.py +++ b/unittest/python/test_std_vector.py @@ -14,6 +14,7 @@ l3.append(np.eye(2)) l4 = [np.random.randn(3, 3).T for _ in range(3)] l4[-1] = l4[-1].T +l5 = [np.random.randn(2, 2).T for _ in range(3)] def checkAllValues(li1, li2): @@ -83,3 +84,12 @@ def checkZero(l): # vector.setZero(l4) # pprint.pprint(list(l4)) # checkZero(l4) + +# TODO fail +l5_copy = std_vector.StdVec_Mat2d(l5) + +# test l5 == l5_copy == l5_py +l5_py = l5_copy.tolist() +# Test l5[0] is zero +l5[0].setZero() +# TODO test diff --git a/unittest/std_vector.cpp b/unittest/std_vector.cpp index fa8a95e8a..225eae1eb 100644 --- a/unittest/std_vector.cpp +++ b/unittest/std_vector.cpp @@ -49,4 +49,12 @@ BOOST_PYTHON_MODULE(std_vector) { typedef Eigen::Ref RefXd; StdVectorPythonVisitor, true>::expose("StdVec_MatRef"); bp::def("setZero", setZero, "Sets the coeffs to 0."); + + // Test matrix modification + // Mat2d don't have tolist, reserve, mutable __getitem__ and from list + // conversion exposeStdVectorEigenSpecificType must add those methods to Mat2d + bp::class_ >("StdVec_Mat2d") + .def(boost::python::vector_indexing_suite< + std::vector >()); + exposeStdVectorEigenSpecificType("Mat2d"); } From 32515e72e7ca25de98f85854bd934d02f66b467e Mon Sep 17 00:00:00 2001 From: Joris Vaillant Date: Wed, 25 Oct 2023 10:06:44 +0200 Subject: [PATCH 2/4] core: Fix std_vector unit tests --- unittest/python/test_std_vector.py | 16 ++++++++++------ unittest/std_vector.cpp | 5 +++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py index 532b7df80..b34a32cb9 100644 --- a/unittest/python/test_std_vector.py +++ b/unittest/python/test_std_vector.py @@ -85,11 +85,15 @@ def checkZero(l): # pprint.pprint(list(l4)) # checkZero(l4) -# TODO fail -l5_copy = std_vector.StdVec_Mat2d(l5) +# Test StdVec_Mat2d that had been registered +# before calling exposeStdVectorEigenSpecificType -# test l5 == l5_copy == l5_py +# Test conversion and tolistl5 == l5_copy == l5_py +l5_copy = std_vector.StdVec_Mat2d(l5) l5_py = l5_copy.tolist() -# Test l5[0] is zero -l5[0].setZero() -# TODO test +checkAllValues(l5, l5_copy) +checkAllValues(l5, l5_py) + +# Test mutable __getitem__ +l5[0][:] = 0.0 +assert np.allclose(l5[0], 0.0) diff --git a/unittest/std_vector.cpp b/unittest/std_vector.cpp index 225eae1eb..942d4ae4e 100644 --- a/unittest/std_vector.cpp +++ b/unittest/std_vector.cpp @@ -52,9 +52,10 @@ BOOST_PYTHON_MODULE(std_vector) { // Test matrix modification // Mat2d don't have tolist, reserve, mutable __getitem__ and from list - // conversion exposeStdVectorEigenSpecificType must add those methods to Mat2d + // conversion + // exposeStdVectorEigenSpecificType must add those methods to StdVec_Mat2d bp::class_ >("StdVec_Mat2d") .def(boost::python::vector_indexing_suite< std::vector >()); - exposeStdVectorEigenSpecificType("Mat2d"); + exposeStdVectorEigenSpecificType("Mat2d"); } From 5f1cc0ee347d5da352d75f13f3989c8d5225e4e5 Mon Sep 17 00:00:00 2001 From: Joris Vaillant Date: Wed, 25 Oct 2023 11:59:49 +0200 Subject: [PATCH 3/4] core: Preserve the old API and simplify some inheritance --- include/eigenpy/std-vector.hpp | 98 ++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index e5a1894a7..77f78c951 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -352,60 +352,64 @@ struct contains_vector_derived_policies return contains_algo::run(container, key); } }; -} // namespace internal - -struct EmptyPythonVisitor - : public ::boost::python::def_visitor { - template - void visit(classT &) const {} -}; +/// /// \brief Add standard method to a std::vector. -template -struct add_std_method_to_std_vector +/// \tparam NoProxy When set to false, the elements will be copied when +/// returned to Python. +/// +template +struct AddStdMethodToStdVector : public boost::python::def_visitor< - add_std_method_to_std_vector > { - typedef typename Container::value_type value_type; - typedef typename Container::value_type data_type; - typedef size_t index_type; - + AddStdMethodToStdVector > { typedef StdContainerFromPythonList FromPythonListConverter; + AddStdMethodToStdVector(const CoVisitor &co_visitor) + : m_co_visitor(co_visitor) {} + template void visit(Class &cl) const { - details::overload_base_get_item_for_std_vector get_item_visitor; - cl.def("tolist", &FromPythonListConverter::tolist, bp::arg("self"), - "Returns the std::vector as a Python list.") - .def(get_item_visitor) + cl.def(m_co_visitor) + .def("tolist", &FromPythonListConverter::tolist, bp::arg("self"), + "Returns the std::vector as a Python list.") .def("reserve", &Container::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(CopyableVisitor()); } + + const CoVisitor &m_co_visitor; +}; + +/// Helper to ease AddStdMethodToStdVector construction +template +static AddStdMethodToStdVector +createAddStdMethodToStdVector(const CoVisitor &co_visitor) { + return AddStdMethodToStdVector(co_visitor); +} + +} // namespace internal + +struct EmptyPythonVisitor + : public ::boost::python::def_visitor { + template + void visit(classT &) const {} }; /// /// \brief Expose an std::vector from a type given as template argument. -/// -/// \tparam T Type to expose as std::vector. -/// \tparam Allocator Type for the Allocator in std::vector. +/// \tparam vector_type std::vector type to expose /// \tparam NoProxy When set to false, the elements will be copied when -/// returned to Python. \tparam EnableFromPythonListConverter Enables the +/// returned to Python. +/// \tparam EnableFromPythonListConverter Enables the /// conversion from a Python list to a std::vector /// -/// \sa StdAlignedVectorPythonVisitor -/// template -struct StdVectorPythonVisitor - : public ::boost::python::vector_indexing_suite< - vector_type, NoProxy, - internal::contains_vector_derived_policies >, - public StdContainerFromPythonList { +struct StdVectorPythonVisitor { typedef typename vector_type::value_type value_type; - typedef typename vector_type::allocator_type allocator_type; typedef StdContainerFromPythonList FromPythonListConverter; @@ -422,21 +426,34 @@ struct StdVectorPythonVisitor template static void expose(const std::string &class_name, const std::string &doc_string, const Visitor &visitor) { - if (!register_symbolic_link_to_registered_type(visitor)) { + // Apply visitor on already registered type or if type is not already + // registered, we define and apply the visitor on it + auto add_std_visitor = + internal::createAddStdMethodToStdVector(visitor); + if (!register_symbolic_link_to_registered_type( + add_std_visitor)) { bp::class_ cl(class_name.c_str(), doc_string.c_str()); - cl.def(StdVectorPythonVisitor()) - .def(bp::init( - bp::args("self", "size", "value"), - "Constructor from a given size and a given value.")) + // Standard vector indexing definition + boost::python::vector_indexing_suite< + vector_type, NoProxy, + internal::contains_vector_derived_policies > + vector_indexing; + + cl.def(bp::init( + bp::args("self", "size", "value"), + "Constructor from a given size and a given value.")) .def(bp::init(bp::args("self", "other"), "Copy constructor")) - .def(visitor) + .def(vector_indexing) + .def(add_std_visitor) .def_pickle(PickleVector()); } - // Register conversion - FromPythonListConverter::register_converter(); + if (EnableFromPythonListConverter) { + // Register conversion + FromPythonListConverter::register_converter(); + } } }; @@ -450,8 +467,9 @@ void exposeStdVectorEigenSpecificType(const char *name) { typedef std::vector > VecMatType; std::string full_name = "StdVec_"; full_name += name; - StdVectorPythonVisitor::expose( - full_name.c_str(), add_std_method_to_std_vector()); + StdVectorPythonVisitor::expose( + full_name.c_str(), + details::overload_base_get_item_for_std_vector()); } } // namespace eigenpy From 06b89f15851bd481a6ab87f530b9e308bf5dd82b Mon Sep 17 00:00:00 2001 From: Joris Vaillant Date: Wed, 8 Nov 2023 16:16:48 +0100 Subject: [PATCH 4/4] Rename AddStdMethodToStdVector to ExposeStdMethodToStdVector --- include/eigenpy/std-vector.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index 77f78c951..dc191a9f9 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -359,13 +359,13 @@ struct contains_vector_derived_policies /// returned to Python. /// template -struct AddStdMethodToStdVector +struct ExposeStdMethodToStdVector : public boost::python::def_visitor< - AddStdMethodToStdVector > { + ExposeStdMethodToStdVector > { typedef StdContainerFromPythonList FromPythonListConverter; - AddStdMethodToStdVector(const CoVisitor &co_visitor) + ExposeStdMethodToStdVector(const CoVisitor &co_visitor) : m_co_visitor(co_visitor) {} template @@ -383,11 +383,11 @@ struct AddStdMethodToStdVector const CoVisitor &m_co_visitor; }; -/// Helper to ease AddStdMethodToStdVector construction +/// Helper to ease ExposeStdMethodToStdVector construction template -static AddStdMethodToStdVector -createAddStdMethodToStdVector(const CoVisitor &co_visitor) { - return AddStdMethodToStdVector(co_visitor); +static ExposeStdMethodToStdVector +createExposeStdMethodToStdVector(const CoVisitor &co_visitor) { + return ExposeStdMethodToStdVector(co_visitor); } } // namespace internal @@ -429,7 +429,8 @@ struct StdVectorPythonVisitor { // Apply visitor on already registered type or if type is not already // registered, we define and apply the visitor on it auto add_std_visitor = - internal::createAddStdMethodToStdVector(visitor); + internal::createExposeStdMethodToStdVector( + visitor); if (!register_symbolic_link_to_registered_type( add_std_visitor)) { bp::class_ cl(class_name.c_str(), doc_string.c_str());