Skip to content

Commit 0718603

Browse files
authored
Merge pull request #412 from ManifoldFR/topic/wjallet/expose-std-array
Expose std::array types
2 parents 4981f02 + ff7517a commit 0718603

File tree

7 files changed

+374
-1
lines changed

7 files changed

+374
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
### Added
10+
- Support for C++11 `std::array` types ([#412](https://github.com/stack-of-tasks/pull/412))
11+
912
## [3.1.4] - 2023-11-27
1013

1114
### Added

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ set(${PROJECT_NAME}_HEADERS
162162
include/eigenpy/user-type.hpp
163163
include/eigenpy/ufunc.hpp
164164
include/eigenpy/register.hpp
165+
include/eigenpy/std-array.hpp
165166
include/eigenpy/std-map.hpp
166167
include/eigenpy/std-vector.hpp
167168
include/eigenpy/optional.hpp

include/eigenpy/std-array.hpp

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/// Copyright (c) 2023 CNRS INRIA
2+
3+
#ifndef __eigenpy_utils_std_array_hpp__
4+
#define __eigenpy_utils_std_array_hpp__
5+
6+
#include <boost/python/suite/indexing/indexing_suite.hpp>
7+
#include "eigenpy/std-vector.hpp"
8+
9+
#include <array>
10+
11+
namespace eigenpy {
12+
13+
template <typename Container, bool NoProxy, class SliceAllocator,
14+
class DerivedPolicies>
15+
class array_indexing_suite;
16+
namespace details {
17+
18+
template <typename Container, bool NoProxy, class SliceAllocator>
19+
class final_array_derived_policies
20+
: public array_indexing_suite<
21+
Container, NoProxy, SliceAllocator,
22+
final_array_derived_policies<Container, NoProxy, SliceAllocator> > {};
23+
} // namespace details
24+
25+
template <typename Container, bool NoProxy = false,
26+
class SliceAllocator = std::allocator<typename Container::value_type>,
27+
class DerivedPolicies = details::final_array_derived_policies<
28+
Container, NoProxy, SliceAllocator> >
29+
class array_indexing_suite
30+
: public bp::vector_indexing_suite<Container, NoProxy, DerivedPolicies> {
31+
public:
32+
typedef typename Container::value_type data_type;
33+
typedef typename Container::value_type key_type;
34+
typedef typename Container::size_type index_type;
35+
typedef typename Container::size_type size_type;
36+
typedef typename Container::difference_type difference_type;
37+
typedef std::vector<data_type, SliceAllocator> slice_vector_type;
38+
static constexpr std::size_t Size = std::tuple_size<Container>{};
39+
40+
template <class Class>
41+
static void extension_def(Class &) {}
42+
43+
// throws exception
44+
static void delete_item(Container &, index_type) {
45+
PyErr_SetString(PyExc_NotImplementedError,
46+
"Cannot delete item from std::array type.");
47+
bp::throw_error_already_set();
48+
}
49+
50+
// throws exception
51+
static void delete_slice(Container &, index_type, index_type) {
52+
PyErr_SetString(PyExc_NotImplementedError,
53+
"Cannot delete slice from std::array type.");
54+
bp::throw_error_already_set();
55+
}
56+
57+
static void set_slice(Container &container, index_type from, index_type to,
58+
data_type const &v) {
59+
if (from >= to) {
60+
PyErr_SetString(PyExc_NotImplementedError,
61+
"Setting this slice would insert into an std::array, "
62+
"which is not supported.");
63+
bp::throw_error_already_set();
64+
} else {
65+
std::fill(container.begin() + from, container.begin() + to, v);
66+
}
67+
}
68+
69+
template <class Iter>
70+
static void set_slice(Container &container, index_type from, index_type to,
71+
Iter first, Iter last) {
72+
if (from >= to) {
73+
PyErr_SetString(PyExc_NotImplementedError,
74+
"Setting this slice would insert into an std::array, "
75+
"which is not supported.");
76+
bp::throw_error_already_set();
77+
} else {
78+
if (long(to - from) == std::distance(first, last)) {
79+
std::copy(first, last, container.begin() + from);
80+
} else {
81+
PyErr_SetString(PyExc_NotImplementedError,
82+
"Size of std::array slice and size of right-hand side "
83+
"iterator are incompatible.");
84+
bp::throw_error_already_set();
85+
}
86+
}
87+
}
88+
89+
static bp::object get_slice(Container &container, index_type from,
90+
index_type to) {
91+
if (from > to) return bp::object(slice_vector_type());
92+
slice_vector_type out;
93+
for (size_t i = from; i < to; i++) {
94+
out.push_back(container[i]);
95+
}
96+
return bp::object(std::move(out));
97+
}
98+
};
99+
100+
/// \brief Expose an std::array (a C++11 fixed-size array) from a given type
101+
/// \tparam array_type std::array type to expose
102+
/// \tparam NoProxy When set to false, the elements will be copied when
103+
/// returned to Python.
104+
/// \tparam SliceAllocator Allocator type to use for slices of std::array type
105+
/// accessed using e.g. __getitem__[0:4] in Python. These slices are returned as
106+
/// std::vector (dynamic size).
107+
template <typename array_type, bool NoProxy = false,
108+
class SliceAllocator =
109+
std::allocator<typename array_type::value_type> >
110+
struct StdArrayPythonVisitor {
111+
typedef typename array_type::value_type value_type;
112+
113+
static ::boost::python::list tolist(array_type &self) {
114+
return details::build_list<array_type, NoProxy>::run(self);
115+
}
116+
117+
static void expose(const std::string &class_name,
118+
const std::string &doc_string = "") {
119+
expose(class_name, doc_string, EmptyPythonVisitor());
120+
}
121+
122+
template <typename DerivedVisitor>
123+
static void expose(const std::string &class_name,
124+
const bp::def_visitor<DerivedVisitor> &visitor) {
125+
expose(class_name, "", visitor);
126+
}
127+
128+
template <typename DerivedVisitor>
129+
static void expose(const std::string &class_name,
130+
const std::string &doc_string,
131+
const bp::def_visitor<DerivedVisitor> &visitor) {
132+
if (!register_symbolic_link_to_registered_type<array_type>()) {
133+
bp::class_<array_type> cl(class_name.c_str(), doc_string.c_str());
134+
cl.def(bp::init<const array_type &>(bp::args("self", "other"),
135+
"Copy constructor"));
136+
137+
array_indexing_suite<array_type, NoProxy, SliceAllocator> indexing_suite;
138+
cl.def(indexing_suite)
139+
.def(visitor)
140+
.def("tolist", tolist, bp::arg("self"),
141+
"Returns the std::array as a Python list.");
142+
}
143+
}
144+
};
145+
146+
/// Exposes std::array<MatrixType, Size>
147+
template <typename MatrixType, std::size_t Size>
148+
void exposeStdArrayEigenSpecificType(const char *name) {
149+
std::ostringstream oss;
150+
oss << "StdArr";
151+
oss << Size << "_" << name;
152+
typedef std::array<MatrixType, Size> array_type;
153+
StdArrayPythonVisitor<array_type, false,
154+
Eigen::aligned_allocator<MatrixType> >::
155+
expose(oss.str(),
156+
details::overload_base_get_item_for_std_vector<array_type>());
157+
}
158+
159+
} // namespace eigenpy
160+
161+
#endif // ifndef __eigenpy_utils_std_array_hpp__

include/eigenpy/std-vector.hpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,20 @@ struct reference_arg_from_python<std::vector<Type, Allocator> &>
234234

235235
namespace eigenpy {
236236

237+
namespace details {
238+
/// Defines traits for the container, used in \struct StdContainerFromPythonList
239+
template <class Container>
240+
struct container_traits {
241+
// default behavior expects allocators
242+
typedef typename Container::allocator_type Allocator;
243+
};
244+
245+
template <typename _Tp, std::size_t Size>
246+
struct container_traits<std::array<_Tp, Size> > {
247+
typedef void Allocator;
248+
};
249+
}; // namespace details
250+
237251
///
238252
/// \brief Register the conversion from a Python list to a std::vector
239253
///
@@ -242,7 +256,7 @@ namespace eigenpy {
242256
template <typename vector_type, bool NoProxy>
243257
struct StdContainerFromPythonList {
244258
typedef typename vector_type::value_type T;
245-
typedef typename vector_type::allocator_type Allocator;
259+
typedef typename details::container_traits<vector_type>::Allocator Allocator;
246260

247261
/// \brief Check if obj_ptr can be converted
248262
static void *convertible(PyObject *obj_ptr) {

unittest/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ if(NOT NUMPY_WITH_BROKEN_UFUNC_SUPPORT)
3838
add_lib_unit_test(user_type)
3939
endif()
4040
add_lib_unit_test(std_vector)
41+
add_lib_unit_test(std_array)
4142
add_lib_unit_test(user_struct)
4243

4344
function(config_bind_optional tagname opttype)
@@ -110,6 +111,10 @@ add_python_unit_test("py-std-vector" "unittest/python/test_std_vector.py"
110111
"python;unittest")
111112
set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
112113

114+
add_python_unit_test("py-std-array" "unittest/python/test_std_array.py"
115+
"python;unittest")
116+
set_tests_properties("py-std-array" PROPERTIES DEPENDS ${PYWRAP})
117+
113118
add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
114119
"python;unittest")
115120
set_tests_properties("py-user-struct" PROPERTIES DEPENDS ${PYWRAP})

unittest/python/test_std_array.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import std_array
2+
3+
4+
ints = std_array.get_arr_3_ints()
5+
print(ints[0])
6+
print(ints[1])
7+
print(ints[2])
8+
print(ints.tolist())
9+
assert ints.tolist() == [1, 2, 3]
10+
11+
_ints_slice = ints[1:3]
12+
print("Printing slice...")
13+
for el in _ints_slice:
14+
print(el)
15+
16+
ref = [1, 2, 3]
17+
assert len(ref[1:2]) == 1 # sanity check
18+
19+
assert len(_ints_slice) == 2, "Slice size should be 1, got %d" % len(_ints_slice)
20+
assert _ints_slice[0] == 2
21+
assert _ints_slice[1] == 3
22+
23+
# Test that insert/delete is impossible with the slice operator
24+
25+
# prepend
26+
try:
27+
ints[0:0] = [0, 1]
28+
except NotImplementedError:
29+
pass
30+
else:
31+
assert False, "Insert value with slice operator should be impossible"
32+
33+
# append
34+
try:
35+
ints[10:12] = [0]
36+
except NotImplementedError:
37+
pass
38+
else:
39+
assert False, "Insert value with slice operator should be impossible"
40+
41+
# append
42+
try:
43+
ints[3:3] = [0]
44+
except NotImplementedError:
45+
pass
46+
else:
47+
assert False, "Insert value with slice operator should be impossible"
48+
49+
# Erase two elements and replace by one
50+
try:
51+
ints[1:3] = [0]
52+
except NotImplementedError:
53+
pass
54+
else:
55+
assert False, "Insert value with slice operator should be impossible"
56+
57+
# Erase two elements and replace by three
58+
try:
59+
ints[1:3] = [0, 1, 2]
60+
except NotImplementedError:
61+
pass
62+
else:
63+
assert False, "Insert value with slice operator should be impossible"
64+
65+
# Test that delete operator is not implemented
66+
# Index delete
67+
try:
68+
del ints[0]
69+
except NotImplementedError:
70+
pass
71+
else:
72+
assert False, "del is not implemented"
73+
74+
# Slice delete
75+
try:
76+
del ints[1:3]
77+
except NotImplementedError:
78+
pass
79+
else:
80+
assert False, "del is not implemented"
81+
82+
# Slice delete
83+
try:
84+
del ints[1:3]
85+
except NotImplementedError:
86+
pass
87+
else:
88+
assert False, "del is not implemented"
89+
90+
# Test that append/extend are not implemented
91+
# append
92+
try:
93+
ints.append(4)
94+
except AttributeError:
95+
pass
96+
else:
97+
assert False, "append is not implemented"
98+
99+
# extend
100+
try:
101+
ints.extend([4, 5])
102+
except AttributeError:
103+
pass
104+
else:
105+
assert False, "extend is not implemented"
106+
107+
# Test set_slice nominal case
108+
ints[1:3] = [10, 20]
109+
assert ints[1] == 10
110+
assert ints[2] == 20
111+
112+
# print(ints.tolist())
113+
114+
vecs = std_array.get_arr_3_vecs()
115+
assert len(vecs) == 3
116+
print(vecs[0])
117+
print(vecs[1])
118+
print(vecs[2])
119+
120+
# slices do not work for Eigen objects...
121+
122+
# v2 = vecs[:]
123+
# assert isinstance(v2, std_array.StdVec_VectorXd)
124+
# assert len(v2) == 3
125+
# print(v2.tolist())
126+
# print(v2[0])
127+
128+
ts = std_array.test_struct()
129+
assert len(ts.integs) == 3
130+
assert len(ts.vecs) == 2
131+
print(ts.integs[:].tolist())
132+
print(ts.vecs[0])
133+
print(ts.vecs[1])
134+
135+
ts.integs[:] = 111
136+
print("Test of set_slice for std::array<int>:", ts.integs[:].tolist())
137+
for el in ts.integs:
138+
assert el == 111
139+
140+
ts.vecs[0][0] = 0.0
141+
ts.vecs[1][0] = -243
142+
print(ts.vecs[0])
143+
print(ts.vecs[1])

0 commit comments

Comments
 (0)