Skip to content

Commit e7bd407

Browse files
authored
Merge pull request #393 from jorisv/topic/override_std_def
Topic/override std def
2 parents 6bca832 + 06b89f1 commit e7bd407

File tree

5 files changed

+244
-40
lines changed

5 files changed

+244
-40
lines changed

include/eigenpy/registration.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define __eigenpy_registration_hpp__
88

99
#include "eigenpy/fwd.hpp"
10+
#include "eigenpy/registration_class.hpp"
1011

1112
namespace eigenpy {
1213

@@ -50,6 +51,25 @@ inline bool register_symbolic_link_to_registered_type() {
5051

5152
return false;
5253
}
54+
55+
/// Same as \see register_symbolic_link_to_registered_type() but apply \p
56+
/// visitor on \tparam T if it already exists
57+
template <typename T, typename Visitor>
58+
inline bool register_symbolic_link_to_registered_type(const Visitor& visitor) {
59+
if (eigenpy::check_registration<T>()) {
60+
const bp::type_info info = bp::type_id<T>();
61+
const bp::converter::registration* reg =
62+
bp::converter::registry::query(info);
63+
bp::handle<> class_obj(reg->get_class_object());
64+
bp::object object(class_obj);
65+
bp::scope().attr(reg->get_class_object()->tp_name) = object;
66+
registration_class<T> cl(object);
67+
cl.def(visitor);
68+
return true;
69+
}
70+
71+
return false;
72+
}
5373
} // namespace eigenpy
5474

5575
#endif // ifndef __eigenpy_registration_hpp__
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2023, INRIA
3+
*/
4+
5+
#ifndef __eigenpy_registration_class_hpp__
6+
#define __eigenpy_registration_class_hpp__
7+
8+
#include <boost/python/class.hpp>
9+
10+
#include "eigenpy/fwd.hpp"
11+
12+
namespace eigenpy {
13+
14+
/*! Copy of the \see boost::python::class_
15+
* This class allow to add methods to an existing class without registering it
16+
* again.
17+
**/
18+
template <class W>
19+
class registration_class {
20+
public:
21+
using self = registration_class;
22+
23+
/// \p object Hold the namespace of the class that will be modified
24+
registration_class(bp::object object) : m_object(object) {}
25+
26+
/// \see boost::python::class_::def(bp::def_visitor<Derived> const& visitor)
27+
template <class Visitor>
28+
self& def(Visitor const& visitor) {
29+
visitor.visit(*this);
30+
return *this;
31+
}
32+
33+
/// \see boost::python::class_::def(char const* name, F f)
34+
template <class F>
35+
self& def(char const* name, F f) {
36+
def_impl(bp::detail::unwrap_wrapper((W*)0), name, f,
37+
bp::detail::def_helper<char const*>(0), &f);
38+
return *this;
39+
}
40+
41+
/// \see boost::python::class_::def(char const* name, A1 a1, A2 const& a2)
42+
template <class A1, class A2>
43+
self& def(char const* name, A1 a1, A2 const& a2) {
44+
def_maybe_overloads(name, a1, a2, &a2);
45+
return *this;
46+
}
47+
48+
/// \see boost::python::class_::def(char const* name, Fn fn, A1 const& a1, A2
49+
/// const& a2)
50+
template <class Fn, class A1, class A2>
51+
self& def(char const* name, Fn fn, A1 const& a1, A2 const& a2) {
52+
def_impl(bp::detail::unwrap_wrapper((W*)0), name, fn,
53+
bp::detail::def_helper<A1, A2>(a1, a2), &fn);
54+
55+
return *this;
56+
}
57+
58+
/// \see boost::python::class_::def(char const* name, Fn fn, A1 const& a1, A2
59+
/// const& a2, A3 const& a3)
60+
template <class Fn, class A1, class A2, class A3>
61+
self& def(char const* name, Fn fn, A1 const& a1, A2 const& a2, A3 const& a3) {
62+
def_impl(bp::detail::unwrap_wrapper((W*)0), name, fn,
63+
bp::detail::def_helper<A1, A2, A3>(a1, a2, a3), &fn);
64+
65+
return *this;
66+
}
67+
68+
private:
69+
/// \see boost::python::class_::def_impl(T*, char const* name, Fn fn, Helper
70+
/// const& helper, ...)
71+
template <class T, class Fn, class Helper>
72+
inline void def_impl(T*, char const* name, Fn fn, Helper const& helper, ...) {
73+
bp::objects::add_to_namespace(
74+
m_object, name,
75+
make_function(fn, helper.policies(), helper.keywords(),
76+
bp::detail::get_signature(fn, (T*)0)),
77+
helper.doc());
78+
79+
def_default(name, fn, helper,
80+
boost::mpl::bool_<Helper::has_default_implementation>());
81+
}
82+
83+
/// \see boost::python::class_::def_default(char const* name, Fn, Helper
84+
/// const& helper, boost::mpl::bool_<true>)
85+
template <class Fn, class Helper>
86+
inline void def_default(char const* name, Fn, Helper const& helper,
87+
boost::mpl::bool_<true>) {
88+
bp::detail::error::virtual_function_default<
89+
W, Fn>::must_be_derived_class_member(helper.default_implementation());
90+
91+
bp::objects::add_to_namespace(
92+
m_object, name,
93+
make_function(helper.default_implementation(), helper.policies(),
94+
helper.keywords()));
95+
}
96+
97+
/// \see boost::python::class_::def_default(char const*, Fn, Helper const&,
98+
/// boost::mpl::bool_<false>)
99+
template <class Fn, class Helper>
100+
inline void def_default(char const*, Fn, Helper const&,
101+
boost::mpl::bool_<false>) {}
102+
103+
/// \see boost::python::class_::def_maybe_overloads(char const* name, SigT
104+
/// sig,OverloadsT const& overloads,bp::detail::overloads_base const*)
105+
template <class OverloadsT, class SigT>
106+
void def_maybe_overloads(char const* name, SigT sig,
107+
OverloadsT const& overloads,
108+
bp::detail::overloads_base const*)
109+
110+
{
111+
bp::detail::define_with_defaults(name, overloads, *this,
112+
bp::detail::get_signature(sig));
113+
}
114+
115+
/// \see boost::python::class_::def_maybe_overloads(char const* name, Fn fn,
116+
/// A1 const& a1, ...)
117+
template <class Fn, class A1>
118+
void def_maybe_overloads(char const* name, Fn fn, A1 const& a1, ...) {
119+
def_impl(bp::detail::unwrap_wrapper((W*)0), name, fn,
120+
bp::detail::def_helper<A1>(a1), &fn);
121+
}
122+
123+
private:
124+
bp::object m_object;
125+
};
126+
127+
} // namespace eigenpy
128+
129+
#endif // ifndef __eigenpy_registration_class_hpp__

include/eigenpy/std-vector.hpp

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,44 @@ struct contains_vector_derived_policies
352352
return contains_algo<key_type>::run(container, key);
353353
}
354354
};
355+
356+
///
357+
/// \brief Add standard method to a std::vector.
358+
/// \tparam NoProxy When set to false, the elements will be copied when
359+
/// returned to Python.
360+
///
361+
template <typename Container, bool NoProxy, typename CoVisitor>
362+
struct ExposeStdMethodToStdVector
363+
: public boost::python::def_visitor<
364+
ExposeStdMethodToStdVector<Container, NoProxy, CoVisitor> > {
365+
typedef StdContainerFromPythonList<Container, NoProxy>
366+
FromPythonListConverter;
367+
368+
ExposeStdMethodToStdVector(const CoVisitor &co_visitor)
369+
: m_co_visitor(co_visitor) {}
370+
371+
template <class Class>
372+
void visit(Class &cl) const {
373+
cl.def(m_co_visitor)
374+
.def("tolist", &FromPythonListConverter::tolist, bp::arg("self"),
375+
"Returns the std::vector as a Python list.")
376+
.def("reserve", &Container::reserve,
377+
(bp::arg("self"), bp::arg("new_cap")),
378+
"Increase the capacity of the vector to a value that's greater "
379+
"or equal to new_cap.")
380+
.def(CopyableVisitor<Container>());
381+
}
382+
383+
const CoVisitor &m_co_visitor;
384+
};
385+
386+
/// Helper to ease ExposeStdMethodToStdVector construction
387+
template <typename Container, bool NoProxy, typename CoVisitor>
388+
static ExposeStdMethodToStdVector<Container, NoProxy, CoVisitor>
389+
createExposeStdMethodToStdVector(const CoVisitor &co_visitor) {
390+
return ExposeStdMethodToStdVector<Container, NoProxy, CoVisitor>(co_visitor);
391+
}
392+
355393
} // namespace internal
356394

357395
struct EmptyPythonVisitor
@@ -362,24 +400,16 @@ struct EmptyPythonVisitor
362400

363401
///
364402
/// \brief Expose an std::vector from a type given as template argument.
365-
///
366-
/// \tparam T Type to expose as std::vector<T>.
367-
/// \tparam Allocator Type for the Allocator in std::vector<T,Allocator>.
368-
/// \tparam NoProxy When set to false, the elements will be copied when returned
369-
/// to Python. \tparam EnableFromPythonListConverter Enables the conversion from
370-
/// a Python list to a std::vector<T,Allocator>
371-
///
372-
/// \sa StdAlignedVectorPythonVisitor
403+
/// \tparam vector_type std::vector type to expose
404+
/// \tparam NoProxy When set to false, the elements will be copied when
405+
/// returned to Python.
406+
/// \tparam EnableFromPythonListConverter Enables the
407+
/// conversion from a Python list to a std::vector<T,Allocator>
373408
///
374409
template <class vector_type, bool NoProxy = false,
375410
bool EnableFromPythonListConverter = true>
376-
struct StdVectorPythonVisitor
377-
: public ::boost::python::vector_indexing_suite<
378-
vector_type, NoProxy,
379-
internal::contains_vector_derived_policies<vector_type, NoProxy> >,
380-
public StdContainerFromPythonList<vector_type, NoProxy> {
411+
struct StdVectorPythonVisitor {
381412
typedef typename vector_type::value_type value_type;
382-
typedef typename vector_type::allocator_type allocator_type;
383413
typedef StdContainerFromPythonList<vector_type, NoProxy>
384414
FromPythonListConverter;
385415

@@ -388,40 +418,42 @@ struct StdVectorPythonVisitor
388418
expose(class_name, doc_string, EmptyPythonVisitor());
389419
}
390420

391-
template <typename VisitorDerived>
392-
static void expose(
393-
const std::string &class_name,
394-
const boost::python::def_visitor<VisitorDerived> &visitor) {
421+
template <typename Visitor>
422+
static void expose(const std::string &class_name, const Visitor &visitor) {
395423
expose(class_name, "", visitor);
396424
}
397425

398-
template <typename VisitorDerived>
399-
static void expose(
400-
const std::string &class_name, const std::string &doc_string,
401-
const boost::python::def_visitor<VisitorDerived> &visitor) {
402-
if (!register_symbolic_link_to_registered_type<vector_type>()) {
426+
template <typename Visitor>
427+
static void expose(const std::string &class_name,
428+
const std::string &doc_string, const Visitor &visitor) {
429+
// Apply visitor on already registered type or if type is not already
430+
// registered, we define and apply the visitor on it
431+
auto add_std_visitor =
432+
internal::createExposeStdMethodToStdVector<vector_type, NoProxy>(
433+
visitor);
434+
if (!register_symbolic_link_to_registered_type<vector_type>(
435+
add_std_visitor)) {
403436
bp::class_<vector_type> cl(class_name.c_str(), doc_string.c_str());
404-
cl.def(StdVectorPythonVisitor())
405437

406-
.def(bp::init<size_t, const value_type &>(
407-
bp::args("self", "size", "value"),
408-
"Constructor from a given size and a given value."))
438+
// Standard vector indexing definition
439+
boost::python::vector_indexing_suite<
440+
vector_type, NoProxy,
441+
internal::contains_vector_derived_policies<vector_type, NoProxy> >
442+
vector_indexing;
443+
444+
cl.def(bp::init<size_t, const value_type &>(
445+
bp::args("self", "size", "value"),
446+
"Constructor from a given size and a given value."))
409447
.def(bp::init<const vector_type &>(bp::args("self", "other"),
410448
"Copy constructor"))
411449

412-
.def("tolist", &FromPythonListConverter::tolist, bp::arg("self"),
413-
"Returns the std::vector as a Python list.")
414-
.def(visitor)
415-
.def("reserve", &vector_type::reserve,
416-
(bp::arg("self"), bp::arg("new_cap")),
417-
"Increase the capacity of the vector to a value that's greater "
418-
"or equal to new_cap.")
419-
.def_pickle(PickleVector<vector_type>())
420-
.def(CopyableVisitor<vector_type>());
421-
450+
.def(vector_indexing)
451+
.def(add_std_visitor)
452+
.def_pickle(PickleVector<vector_type>());
453+
}
454+
if (EnableFromPythonListConverter) {
422455
// Register conversion
423-
if (EnableFromPythonListConverter)
424-
FromPythonListConverter::register_converter();
456+
FromPythonListConverter::register_converter();
425457
}
426458
}
427459
};
@@ -436,7 +468,7 @@ void exposeStdVectorEigenSpecificType(const char *name) {
436468
typedef std::vector<MatType, Eigen::aligned_allocator<MatType> > VecMatType;
437469
std::string full_name = "StdVec_";
438470
full_name += name;
439-
StdVectorPythonVisitor<VecMatType, false>::expose(
471+
StdVectorPythonVisitor<VecMatType>::expose(
440472
full_name.c_str(),
441473
details::overload_base_get_item_for_std_vector<VecMatType>());
442474
}

unittest/python/test_std_vector.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
l3.append(np.eye(2))
1515
l4 = [np.random.randn(3, 3).T for _ in range(3)]
1616
l4[-1] = l4[-1].T
17+
l5 = [np.random.randn(2, 2).T for _ in range(3)]
1718

1819

1920
def checkAllValues(li1, li2):
@@ -83,3 +84,16 @@ def checkZero(l):
8384
# vector.setZero(l4)
8485
# pprint.pprint(list(l4))
8586
# checkZero(l4)
87+
88+
# Test StdVec_Mat2d that had been registered
89+
# before calling exposeStdVectorEigenSpecificType
90+
91+
# Test conversion and tolistl5 == l5_copy == l5_py
92+
l5_copy = std_vector.StdVec_Mat2d(l5)
93+
l5_py = l5_copy.tolist()
94+
checkAllValues(l5, l5_copy)
95+
checkAllValues(l5, l5_py)
96+
97+
# Test mutable __getitem__
98+
l5[0][:] = 0.0
99+
assert np.allclose(l5[0], 0.0)

unittest/std_vector.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,13 @@ BOOST_PYTHON_MODULE(std_vector) {
4949
typedef Eigen::Ref<Eigen::MatrixXd> RefXd;
5050
StdVectorPythonVisitor<std::vector<RefXd>, true>::expose("StdVec_MatRef");
5151
bp::def("setZero", setZero<Eigen::MatrixXd>, "Sets the coeffs to 0.");
52+
53+
// Test matrix modification
54+
// Mat2d don't have tolist, reserve, mutable __getitem__ and from list
55+
// conversion
56+
// exposeStdVectorEigenSpecificType must add those methods to StdVec_Mat2d
57+
bp::class_<std::vector<Eigen::Matrix2d> >("StdVec_Mat2d")
58+
.def(boost::python::vector_indexing_suite<
59+
std::vector<Eigen::Matrix2d> >());
60+
exposeStdVectorEigenSpecificType<Eigen::Matrix2d>("Mat2d");
5261
}

0 commit comments

Comments
 (0)