Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf02d84
[std-vector] StdContainerFromPythonList : remove Allocator typedef
ManifoldFR Nov 30, 2023
4f07565
Add header eigenpy/std-array.hpp
ManifoldFR Nov 30, 2023
dd8071d
[std-array] array_indexing_suite: fix up set_sice from iterators
ManifoldFR Nov 30, 2023
d0f7674
[unittest] add unit tests for std::array
ManifoldFR Nov 30, 2023
6211cf5
[std-array] array_indexing_suite: get_slice returns std::vector<T>
ManifoldFR Nov 30, 2023
153ba3e
[unittest] std::array test: test for size of _ints_slice
ManifoldFR Nov 30, 2023
f9e84ec
[std-array] add SliceAllocator template parameter
ManifoldFR Nov 30, 2023
a9903ad
[std-array] add SliceAllocator template parameter to StdArrayPythonVi…
ManifoldFR Nov 30, 2023
2cbf10e
[std-array] fix size of returned slice
ManifoldFR Nov 30, 2023
5e60184
[unittest] expose std::vector<VectorXd> for use in slices
ManifoldFR Nov 30, 2023
ced9002
[unittest/python] update test_std_array.py
ManifoldFR Nov 30, 2023
82ec784
[std-array] use visitor in StdArrayPythonVisitor::expose()
ManifoldFR Nov 30, 2023
5d487e8
[unittest/python] update test_std_array.py
ManifoldFR Nov 30, 2023
ba6751b
[unittest] std_array : add test_struct
ManifoldFR Nov 30, 2023
a03deb8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 30, 2023
70bae63
[std-array] add docstring for StdArrayPythonVisitor
ManifoldFR Nov 30, 2023
384c193
[unittest/python] test set_slice on std::array<int, ...>
ManifoldFR Nov 30, 2023
36d2ba3
[cmake] I forgot to add the std-array.hpp header...
ManifoldFR Nov 30, 2023
58571e5
update changelog
ManifoldFR Nov 30, 2023
dc9789e
[std-vector] add container_traits struct
ManifoldFR Dec 4, 2023
f4d61cc
[std-array] defining array_indexing_suite::extend and ::append not re…
ManifoldFR Dec 4, 2023
2c18089
[std-array] array_indexing_suite::delete_item and ::delete_slice will…
ManifoldFR Dec 4, 2023
70e518d
[std-array] various fixes
ManifoldFR Dec 4, 2023
6ad6448
Update CHANGELOG
ManifoldFR Dec 4, 2023
cc30e46
[std-array] Test array edge cases
jorisv Dec 4, 2023
44f8bdc
[std-array] implement tolist() for std::array
ManifoldFR Dec 4, 2023
ff7517a
[unittest/python] test .tolist()
ManifoldFR Dec 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added
- Support for C++11 `std::array` types ([#412](https://github.com/stack-of-tasks/pull/412))

## [3.1.4] - 2023-11-27

### Added
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ set(${PROJECT_NAME}_HEADERS
include/eigenpy/user-type.hpp
include/eigenpy/ufunc.hpp
include/eigenpy/register.hpp
include/eigenpy/std-array.hpp
include/eigenpy/std-map.hpp
include/eigenpy/std-vector.hpp
include/eigenpy/optional.hpp
Expand Down
161 changes: 161 additions & 0 deletions include/eigenpy/std-array.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/// Copyright (c) 2023 CNRS INRIA

#ifndef __eigenpy_utils_std_array_hpp__
#define __eigenpy_utils_std_array_hpp__

#include <boost/python/suite/indexing/indexing_suite.hpp>
#include "eigenpy/std-vector.hpp"

#include <array>

namespace eigenpy {

template <typename Container, bool NoProxy, class SliceAllocator,
class DerivedPolicies>
class array_indexing_suite;
namespace details {

template <typename Container, bool NoProxy, class SliceAllocator>
class final_array_derived_policies
: public array_indexing_suite<
Container, NoProxy, SliceAllocator,
final_array_derived_policies<Container, NoProxy, SliceAllocator> > {};
} // namespace details

template <typename Container, bool NoProxy = false,
class SliceAllocator = std::allocator<typename Container::value_type>,
class DerivedPolicies = details::final_array_derived_policies<
Container, NoProxy, SliceAllocator> >
class array_indexing_suite
: public bp::vector_indexing_suite<Container, NoProxy, DerivedPolicies> {
public:
typedef typename Container::value_type data_type;
typedef typename Container::value_type key_type;
typedef typename Container::size_type index_type;
typedef typename Container::size_type size_type;
typedef typename Container::difference_type difference_type;
typedef std::vector<data_type, SliceAllocator> slice_vector_type;
static constexpr std::size_t Size = std::tuple_size<Container>{};

template <class Class>
static void extension_def(Class &) {}

// throws exception
static void delete_item(Container &, index_type) {
PyErr_SetString(PyExc_NotImplementedError,
"Cannot delete item from std::array type.");
bp::throw_error_already_set();
}

// throws exception
static void delete_slice(Container &, index_type, index_type) {
PyErr_SetString(PyExc_NotImplementedError,
"Cannot delete slice from std::array type.");
bp::throw_error_already_set();
}

static void set_slice(Container &container, index_type from, index_type to,
data_type const &v) {
if (from >= to) {
PyErr_SetString(PyExc_NotImplementedError,
"Setting this slice would insert into an std::array, "
"which is not supported.");
bp::throw_error_already_set();
} else {
std::fill(container.begin() + from, container.begin() + to, v);
}
}

template <class Iter>
static void set_slice(Container &container, index_type from, index_type to,
Iter first, Iter last) {
if (from >= to) {
PyErr_SetString(PyExc_NotImplementedError,
"Setting this slice would insert into an std::array, "
"which is not supported.");
bp::throw_error_already_set();
} else {
if (long(to - from) == std::distance(first, last)) {
std::copy(first, last, container.begin() + from);
} else {
PyErr_SetString(PyExc_NotImplementedError,
"Size of std::array slice and size of right-hand side "
"iterator are incompatible.");
bp::throw_error_already_set();
}
}
}

static bp::object get_slice(Container &container, index_type from,
index_type to) {
if (from > to) return bp::object(slice_vector_type());
slice_vector_type out;
for (size_t i = from; i < to; i++) {
out.push_back(container[i]);
}
return bp::object(std::move(out));
}
};

/// \brief Expose an std::array (a C++11 fixed-size array) from a given type
/// \tparam array_type std::array type to expose
/// \tparam NoProxy When set to false, the elements will be copied when
/// returned to Python.
/// \tparam SliceAllocator Allocator type to use for slices of std::array type
/// accessed using e.g. __getitem__[0:4] in Python. These slices are returned as
/// std::vector (dynamic size).
template <typename array_type, bool NoProxy = false,
class SliceAllocator =
std::allocator<typename array_type::value_type> >
struct StdArrayPythonVisitor {
typedef typename array_type::value_type value_type;

static ::boost::python::list tolist(array_type &self) {
return details::build_list<array_type, NoProxy>::run(self);
}

static void expose(const std::string &class_name,
const std::string &doc_string = "") {
expose(class_name, doc_string, EmptyPythonVisitor());
}

template <typename DerivedVisitor>
static void expose(const std::string &class_name,
const bp::def_visitor<DerivedVisitor> &visitor) {
expose(class_name, "", visitor);
}

template <typename DerivedVisitor>
static void expose(const std::string &class_name,
const std::string &doc_string,
const bp::def_visitor<DerivedVisitor> &visitor) {
if (!register_symbolic_link_to_registered_type<array_type>()) {
bp::class_<array_type> cl(class_name.c_str(), doc_string.c_str());
cl.def(bp::init<const array_type &>(bp::args("self", "other"),
"Copy constructor"));

array_indexing_suite<array_type, NoProxy, SliceAllocator> indexing_suite;
cl.def(indexing_suite)
.def(visitor)
.def("tolist", tolist, bp::arg("self"),
"Returns the std::array as a Python list.");
}
}
};

/// Exposes std::array<MatrixType, Size>
template <typename MatrixType, std::size_t Size>
void exposeStdArrayEigenSpecificType(const char *name) {
std::ostringstream oss;
oss << "StdArr";
oss << Size << "_" << name;
typedef std::array<MatrixType, Size> array_type;
StdArrayPythonVisitor<array_type, false,
Eigen::aligned_allocator<MatrixType> >::
expose(oss.str(),
details::overload_base_get_item_for_std_vector<array_type>());
}

} // namespace eigenpy

#endif // ifndef __eigenpy_utils_std_array_hpp__
16 changes: 15 additions & 1 deletion include/eigenpy/std-vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,20 @@ struct reference_arg_from_python<std::vector<Type, Allocator> &>

namespace eigenpy {

namespace details {
/// Defines traits for the container, used in \struct StdContainerFromPythonList
template <class Container>
struct container_traits {
// default behavior expects allocators
typedef typename Container::allocator_type Allocator;
};

template <typename _Tp, std::size_t Size>
struct container_traits<std::array<_Tp, Size> > {
typedef void Allocator;
};
}; // namespace details

///
/// \brief Register the conversion from a Python list to a std::vector
///
Expand All @@ -242,7 +256,7 @@ namespace eigenpy {
template <typename vector_type, bool NoProxy>
struct StdContainerFromPythonList {
typedef typename vector_type::value_type T;
typedef typename vector_type::allocator_type Allocator;
typedef typename details::container_traits<vector_type>::Allocator Allocator;

/// \brief Check if obj_ptr can be converted
static void *convertible(PyObject *obj_ptr) {
Expand Down
5 changes: 5 additions & 0 deletions unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ if(NOT NUMPY_WITH_BROKEN_UFUNC_SUPPORT)
add_lib_unit_test(user_type)
endif()
add_lib_unit_test(std_vector)
add_lib_unit_test(std_array)
add_lib_unit_test(user_struct)

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

add_python_unit_test("py-std-array" "unittest/python/test_std_array.py"
"python;unittest")
set_tests_properties("py-std-array" PROPERTIES DEPENDS ${PYWRAP})

add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
"python;unittest")
set_tests_properties("py-user-struct" PROPERTIES DEPENDS ${PYWRAP})
Expand Down
143 changes: 143 additions & 0 deletions unittest/python/test_std_array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import std_array


ints = std_array.get_arr_3_ints()
print(ints[0])
print(ints[1])
print(ints[2])
print(ints.tolist())
assert ints.tolist() == [1, 2, 3]

_ints_slice = ints[1:3]
print("Printing slice...")
for el in _ints_slice:
print(el)

ref = [1, 2, 3]
assert len(ref[1:2]) == 1 # sanity check

assert len(_ints_slice) == 2, "Slice size should be 1, got %d" % len(_ints_slice)
assert _ints_slice[0] == 2
assert _ints_slice[1] == 3

# Test that insert/delete is impossible with the slice operator

# prepend
try:
ints[0:0] = [0, 1]
except NotImplementedError:
pass
else:
assert False, "Insert value with slice operator should be impossible"

# append
try:
ints[10:12] = [0]
except NotImplementedError:
pass
else:
assert False, "Insert value with slice operator should be impossible"

# append
try:
ints[3:3] = [0]
except NotImplementedError:
pass
else:
assert False, "Insert value with slice operator should be impossible"

# Erase two elements and replace by one
try:
ints[1:3] = [0]
except NotImplementedError:
pass
else:
assert False, "Insert value with slice operator should be impossible"

# Erase two elements and replace by three
try:
ints[1:3] = [0, 1, 2]
except NotImplementedError:
pass
else:
assert False, "Insert value with slice operator should be impossible"

# Test that delete operator is not implemented
# Index delete
try:
del ints[0]
except NotImplementedError:
pass
else:
assert False, "del is not implemented"

# Slice delete
try:
del ints[1:3]
except NotImplementedError:
pass
else:
assert False, "del is not implemented"

# Slice delete
try:
del ints[1:3]
except NotImplementedError:
pass
else:
assert False, "del is not implemented"

# Test that append/extend are not implemented
# append
try:
ints.append(4)
except AttributeError:
pass
else:
assert False, "append is not implemented"

# extend
try:
ints.extend([4, 5])
except AttributeError:
pass
else:
assert False, "extend is not implemented"

# Test set_slice nominal case
ints[1:3] = [10, 20]
assert ints[1] == 10
assert ints[2] == 20

# print(ints.tolist())

vecs = std_array.get_arr_3_vecs()
assert len(vecs) == 3
print(vecs[0])
print(vecs[1])
print(vecs[2])

# slices do not work for Eigen objects...

# v2 = vecs[:]
# assert isinstance(v2, std_array.StdVec_VectorXd)
# assert len(v2) == 3
# print(v2.tolist())
# print(v2[0])

ts = std_array.test_struct()
assert len(ts.integs) == 3
assert len(ts.vecs) == 2
print(ts.integs[:].tolist())
print(ts.vecs[0])
print(ts.vecs[1])

ts.integs[:] = 111
print("Test of set_slice for std::array<int>:", ts.integs[:].tolist())
for el in ts.integs:
assert el == 111

ts.vecs[0][0] = 0.0
ts.vecs[1][0] = -243
print(ts.vecs[0])
print(ts.vecs[1])
Loading