Skip to content

Commit

Permalink
Merge pull request #393 from jorisv/topic/override_std_def
Browse files Browse the repository at this point in the history
Topic/override std def
  • Loading branch information
jcarpent committed Nov 8, 2023
2 parents 6bca832 + 06b89f1 commit e7bd407
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 40 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__
112 changes: 72 additions & 40 deletions include/eigenpy/std-vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,44 @@ struct contains_vector_derived_policies
return contains_algo<key_type>::run(container, key);
}
};

///
/// \brief Add standard method to a std::vector.
/// \tparam NoProxy When set to false, the elements will be copied when
/// returned to Python.
///
template <typename Container, bool NoProxy, typename CoVisitor>
struct ExposeStdMethodToStdVector
: public boost::python::def_visitor<
ExposeStdMethodToStdVector<Container, NoProxy, CoVisitor> > {
typedef StdContainerFromPythonList<Container, NoProxy>
FromPythonListConverter;

ExposeStdMethodToStdVector(const CoVisitor &co_visitor)
: m_co_visitor(co_visitor) {}

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

const CoVisitor &m_co_visitor;
};

/// Helper to ease ExposeStdMethodToStdVector construction
template <typename Container, bool NoProxy, typename CoVisitor>
static ExposeStdMethodToStdVector<Container, NoProxy, CoVisitor>
createExposeStdMethodToStdVector(const CoVisitor &co_visitor) {
return ExposeStdMethodToStdVector<Container, NoProxy, CoVisitor>(co_visitor);
}

} // namespace internal

struct EmptyPythonVisitor
Expand All @@ -362,24 +400,16 @@ struct EmptyPythonVisitor

///
/// \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
/// \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
/// conversion from a Python list to a std::vector<T,Allocator>
///
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> {
struct StdVectorPythonVisitor {
typedef typename vector_type::value_type value_type;
typedef typename vector_type::allocator_type allocator_type;
typedef StdContainerFromPythonList<vector_type, NoProxy>
FromPythonListConverter;

Expand All @@ -388,40 +418,42 @@ 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) {
// 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::createExposeStdMethodToStdVector<vector_type, NoProxy>(
visitor);
if (!register_symbolic_link_to_registered_type<vector_type>(
add_std_visitor)) {
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."))
// Standard vector indexing definition
boost::python::vector_indexing_suite<
vector_type, NoProxy,
internal::contains_vector_derived_policies<vector_type, NoProxy> >
vector_indexing;

cl.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>());

.def(vector_indexing)
.def(add_std_visitor)
.def_pickle(PickleVector<vector_type>());
}
if (EnableFromPythonListConverter) {
// Register conversion
if (EnableFromPythonListConverter)
FromPythonListConverter::register_converter();
FromPythonListConverter::register_converter();
}
}
};
Expand All @@ -436,7 +468,7 @@ void exposeStdVectorEigenSpecificType(const char *name) {
typedef std::vector<MatType, Eigen::aligned_allocator<MatType> > VecMatType;
std::string full_name = "StdVec_";
full_name += name;
StdVectorPythonVisitor<VecMatType, false>::expose(
StdVectorPythonVisitor<VecMatType>::expose(
full_name.c_str(),
details::overload_base_get_item_for_std_vector<VecMatType>());
}
Expand Down
14 changes: 14 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,16 @@ def checkZero(l):
# vector.setZero(l4)
# pprint.pprint(list(l4))
# checkZero(l4)

# Test StdVec_Mat2d that had been registered
# before calling exposeStdVectorEigenSpecificType

# Test conversion and tolistl5 == l5_copy == l5_py
l5_copy = std_vector.StdVec_Mat2d(l5)
l5_py = l5_copy.tolist()
checkAllValues(l5, l5_copy)
checkAllValues(l5, l5_py)

# Test mutable __getitem__
l5[0][:] = 0.0
assert np.allclose(l5[0], 0.0)
9 changes: 9 additions & 0 deletions unittest/std_vector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,13 @@ 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 StdVec_Mat2d
bp::class_<std::vector<Eigen::Matrix2d> >("StdVec_Mat2d")
.def(boost::python::vector_indexing_suite<
std::vector<Eigen::Matrix2d> >());
exposeStdVectorEigenSpecificType<Eigen::Matrix2d>("Mat2d");
}

0 comments on commit e7bd407

Please sign in to comment.