Skip to content

Commit

Permalink
[WIP] Add methods to already defined vector type
Browse files Browse the repository at this point in the history
  • Loading branch information
Joris Vaillant committed Oct 24, 2023
1 parent 5bbaa59 commit 679e9d6
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 26 deletions.
20 changes: 20 additions & 0 deletions include/eigenpy/registration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define __eigenpy_registration_hpp__

#include "eigenpy/fwd.hpp"
#include "eigenpy/registration_class.hpp"

namespace eigenpy {

Expand Down Expand Up @@ -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 <typename T, typename Visitor>
inline bool register_symbolic_link_to_registered_type(const Visitor& visitor) {
if (eigenpy::check_registration<T>()) {
const bp::type_info info = bp::type_id<T>();
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<T> cl(object);
cl.def(visitor);
return true;
}

return false;
}
} // namespace eigenpy

#endif // ifndef __eigenpy_registration_hpp__
129 changes: 129 additions & 0 deletions include/eigenpy/registration_class.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2023, INRIA
*/

#ifndef __eigenpy_registration_class_hpp__
#define __eigenpy_registration_class_hpp__

#include <boost/python/class.hpp>

#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 W>
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<Derived> const& visitor)
template <class Visitor>
self& def(Visitor const& visitor) {
visitor.visit(*this);
return *this;
}

/// \see boost::python::class_::def(char const* name, F f)
template <class F>
self& def(char const* name, F f) {
def_impl(bp::detail::unwrap_wrapper((W*)0), name, f,
bp::detail::def_helper<char const*>(0), &f);
return *this;
}

/// \see boost::python::class_::def(char const* name, A1 a1, A2 const& a2)
template <class A1, class A2>
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 <class Fn, class A1, class A2>
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>(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 <class Fn, class A1, class A2, class A3>
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>(a1, a2, a3), &fn);

return *this;
}

private:
/// \see boost::python::class_::def_impl(T*, char const* name, Fn fn, Helper
/// const& helper, ...)
template <class T, class Fn, class Helper>
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_<Helper::has_default_implementation>());
}

/// \see boost::python::class_::def_default(char const* name, Fn, Helper
/// const& helper, boost::mpl::bool_<true>)
template <class Fn, class Helper>
inline void def_default(char const* name, Fn, Helper const& helper,
boost::mpl::bool_<true>) {
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_<false>)
template <class Fn, class Helper>
inline void def_default(char const*, Fn, Helper const&,
boost::mpl::bool_<false>) {}

/// \see boost::python::class_::def_maybe_overloads(char const* name, SigT
/// sig,OverloadsT const& overloads,bp::detail::overloads_base const*)
template <class OverloadsT, class SigT>
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 <class Fn, class A1>
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>(a1), &fn);
}

private:
bp::object m_object;
};

} // namespace eigenpy

#endif // ifndef __eigenpy_registration_class_hpp__
65 changes: 39 additions & 26 deletions include/eigenpy/std-vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,14 +360,40 @@ struct EmptyPythonVisitor
void visit(classT &) const {}
};

/// \brief Add standard method to a std::vector.
template <typename Container, bool NoProxy = false>
struct add_std_method_to_std_vector
: public boost::python::def_visitor<
add_std_method_to_std_vector<Container> > {
typedef typename Container::value_type value_type;
typedef typename Container::value_type data_type;
typedef size_t index_type;

typedef StdContainerFromPythonList<Container, NoProxy>
FromPythonListConverter;

template <class Class>
void visit(Class &cl) const {
details::overload_base_get_item_for_std_vector<Container> 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<Container>());
}
};

///
/// \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>
/// \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
///
Expand All @@ -388,18 +414,15 @@ struct StdVectorPythonVisitor
expose(class_name, doc_string, EmptyPythonVisitor());
}

template <typename VisitorDerived>
static void expose(
const std::string &class_name,
const boost::python::def_visitor<VisitorDerived> &visitor) {
template <typename Visitor>
static void expose(const std::string &class_name, const Visitor &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) {
if (!register_symbolic_link_to_registered_type<vector_type>()) {
template <typename Visitor>
static void expose(const std::string &class_name,
const std::string &doc_string, const Visitor &visitor) {
if (!register_symbolic_link_to_registered_type<vector_type>(visitor)) {
bp::class_<vector_type> cl(class_name.c_str(), doc_string.c_str());
cl.def(StdVectorPythonVisitor())

Expand All @@ -409,20 +432,11 @@ struct StdVectorPythonVisitor
.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();
.def_pickle(PickleVector<vector_type>());
}
// Register conversion
FromPythonListConverter::register_converter();
}
};

Expand All @@ -437,8 +451,7 @@ void exposeStdVectorEigenSpecificType(const char *name) {
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>());
full_name.c_str(), add_std_method_to_std_vector<VecMatType, false>());
}

} // namespace eigenpy
Expand Down
10 changes: 10 additions & 0 deletions unittest/python/test_std_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
8 changes: 8 additions & 0 deletions unittest/std_vector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,12 @@ BOOST_PYTHON_MODULE(std_vector) {
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.");

// Test matrix modification
// Mat2d don't have tolist, reserve, mutable __getitem__ and from list
// conversion exposeStdVectorEigenSpecificType must add those methods to Mat2d
bp::class_<std::vector<Eigen::Matrix2d> >("StdVec_Mat2d")
.def(boost::python::vector_indexing_suite<
std::vector<Eigen::Matrix2d> >());
exposeStdVectorEigenSpecificType<Eigen::Matrix3d>("Mat2d");
}

0 comments on commit 679e9d6

Please sign in to comment.