From ebb3fcf1a63ea5888c9064589d97b934952652b3 Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Tue, 23 Sep 2025 14:22:10 +0100 Subject: [PATCH 1/7] [vector] Supported Python sliding in std::vector --- include/eigenpy/std-vector.hpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index f2cf821e0..790d32d8a 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -1,5 +1,6 @@ /// /// Copyright (c) 2016-2024 CNRS INRIA +/// Copyright (c) 2025-2025 Heriot-Watt University /// This file was taken from Pinocchio (header /// ) /// @@ -84,11 +85,12 @@ struct overload_base_get_item_for_std_vector template void visit(Class &cl) const { - cl.def("__getitem__", &base_get_item); + cl.def("__getitem__", &base_get_item_int) + .def("__getitem__", &base_get_item_slice); } private: - static boost::python::object base_get_item( + static boost::python::object base_get_item_int( boost::python::back_reference container, PyObject *i_) { index_type idx = convert_index(container.get(), i_); typename Container::iterator i = container.get().begin(); @@ -104,6 +106,29 @@ struct overload_base_get_item_for_std_vector return bp::object(bp::handle<>(convert(*i))); } + static boost::python::object base_get_item_slice( + boost::python::back_reference container, + boost::python::slice slice) { + + namespace bp = boost::python; + Py_ssize_t start, stop, step, slicelen; + const Py_ssize_t n = static_cast(container.get().size()); + + if (PySlice_GetIndicesEx(slice.ptr(), n, &start, &stop, &step, &slicelen) != 0) + bp::throw_error_already_set(); + + bp::list out; + for (Py_ssize_t k = 0; k < slicelen; ++k) { + Py_ssize_t idx = start + k * step; + + // return a reference (not a copy), like your int-getitem + typename bp::to_python_indirect convert; + out.append(bp::object(bp::handle<>(convert(container.get()[std::size_t(idx)])))); + } + return out; + } + static index_type convert_index(Container &container, PyObject *i_) { bp::extract i(i_); if (i.check()) { From f541e2abb66d4fe959687170e49f9b67f6f8f5d1 Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Tue, 23 Sep 2025 22:02:52 +0100 Subject: [PATCH 2/7] [vector] Replace with slice.get_indices --- include/eigenpy/std-vector.hpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index 790d32d8a..931c025ed 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -111,20 +111,22 @@ struct overload_base_get_item_for_std_vector boost::python::slice slice) { namespace bp = boost::python; - Py_ssize_t start, stop, step, slicelen; - const Py_ssize_t n = static_cast(container.get().size()); - - if (PySlice_GetIndicesEx(slice.ptr(), n, &start, &stop, &step, &slicelen) != 0) - bp::throw_error_already_set(); bp::list out; - for (Py_ssize_t k = 0; k < slicelen; ++k) { - Py_ssize_t idx = start + k * step; - - // return a reference (not a copy), like your int-getitem + try { + auto rng = slice.get_indices(container.get().begin(), container.get().end()); + // rng.start, rng.stop are iterators; rng.step is int; [start, stop] is closed typename bp::to_python_indirect convert; - out.append(bp::object(bp::handle<>(convert(container.get()[std::size_t(idx)])))); + // forward or backward + for (typename Container::iterator it = rng.start;; std::advance(it, rng.step)) { + out.append(bp::object(bp::handle<>(convert(*it)))); + if (it == rng.stop) break; // closed interval, include stop + } + } catch (const std::invalid_argument&) { + // Boost.Python specifies empty ranges throw invalid_argument. + // Return [] (matches Python's behavior for empty slices). + return bp::list(); } return out; } From 7acf581a3498ee2c0721be221abaf045f054543e Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Tue, 23 Sep 2025 22:18:35 +0100 Subject: [PATCH 3/7] [unittest] Added slicing tests --- unittest/python/test_std_vector.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py index 1b0ace614..e398be724 100644 --- a/unittest/python/test_std_vector.py +++ b/unittest/python/test_std_vector.py @@ -98,3 +98,12 @@ def checkZero(v): # Test mutable __getitem__ l5[0][:] = 0.0 assert np.allclose(l5[0], 0.0) + +# Test slicing +l6 = eigenpy.StdVec_VectorXd() +for _ in range(4): + l6.append(np.random.randn(3)) +checkAllValues(l6[:1], l6.tolist()[:1]) +checkAllValues(l6[1:], l6.tolist()[1:]) +checkAllValues(l6[:-1], l6.tolist()[:-1]) +checkAllValues(l6[::2], l6.tolist()[::2]) From 6837ecb65b7f3cdea956ef3823acd71d18a0a681 Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Tue, 23 Sep 2025 22:19:35 +0100 Subject: [PATCH 4/7] [vector] Supported list/tuple indexing in vector slicing --- include/eigenpy/std-vector.hpp | 55 +++++++++++++++++++++++++++--- unittest/python/test_std_vector.py | 8 +++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index 931c025ed..8481e8ec3 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -86,7 +86,9 @@ struct overload_base_get_item_for_std_vector template void visit(Class &cl) const { cl.def("__getitem__", &base_get_item_int) - .def("__getitem__", &base_get_item_slice); + .def("__getitem__", &base_get_item_slice) + .def("__getitem__", &base_get_item_list) + .def("__getitem__", &base_get_item_tuple); } private: @@ -109,9 +111,6 @@ struct overload_base_get_item_for_std_vector static boost::python::object base_get_item_slice( boost::python::back_reference container, boost::python::slice slice) { - - namespace bp = boost::python; - bp::list out; try { auto rng = slice.get_indices(container.get().begin(), container.get().end()); @@ -131,6 +130,54 @@ struct overload_base_get_item_for_std_vector return out; } + static bp::object base_get_item_list(bp::back_reference c, bp::list idxs) { + const Py_ssize_t m = bp::len(idxs); + bp::list out; + for (Py_ssize_t k = 0; k < m; ++k) { + bp::object obj = idxs[k]; + bp::extract ei(obj); + if (!ei.check()) { + PyErr_SetString(PyExc_TypeError, "indices must be integers"); + bp::throw_error_already_set(); + } + auto idx = normalize_index(c.get().size(), ei()); + out.append(elem_ref(c.get(), idx)); + } + return out; + } + + static bp::object base_get_item_tuple(bp::back_reference c, bp::tuple idxs) { + const Py_ssize_t m = bp::len(idxs); + bp::list out; + for (Py_ssize_t k = 0; k < m; ++k) { + bp::object obj = idxs[k]; + bp::extract ei(obj); + if (!ei.check()) { + PyErr_SetString(PyExc_TypeError, "indices must be integers"); + bp::throw_error_already_set(); + } + auto idx = normalize_index(c.get().size(), ei()); + out.append(elem_ref(c.get(), idx)); + } + return out; + } + + static index_type normalize_index(std::size_t n, long i) { + long idx = i; + if (idx < 0) idx += static_cast(n); + if (idx < 0 || idx >= static_cast(n)) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + bp::throw_error_already_set(); + } + return static_cast(idx); + } + + static bp::object elem_ref(Container& c, index_type i) { + typename bp::to_python_indirect conv; + return bp::object(bp::handle<>(conv(c[i]))); + } + static index_type convert_index(Container &container, PyObject *i_) { bp::extract i(i_); if (i.check()) { diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py index e398be724..8bd104eba 100644 --- a/unittest/python/test_std_vector.py +++ b/unittest/python/test_std_vector.py @@ -107,3 +107,11 @@ def checkZero(v): checkAllValues(l6[1:], l6.tolist()[1:]) checkAllValues(l6[:-1], l6.tolist()[:-1]) checkAllValues(l6[::2], l6.tolist()[::2]) +L = [0, 2] +L6_copy = l6[L] +for k, i in enumerate(L): + checkAllValues(L6_copy[k], l6[i]) +T = (0, 2) +L6_copy = l6[T] +for k, i in enumerate(L): + checkAllValues(L6_copy[k], l6[i]) \ No newline at end of file From 788cbce753f2381a402e4e27e17e76ac675cc06a Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Tue, 23 Sep 2025 22:29:20 +0100 Subject: [PATCH 5/7] [changelog] Updated --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc5f2d86..1e658fa14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Changed - - Remove `accelerate.hpp` header that clash with Accelerate.hpp in non case sensitive OS ([#593](https://github.com/stack-of-tasks/eigenpy/pull/593) - We don't consider it an API break since this header was rarely used. + We don't consider it an API break since this header was rarely used. + +### Added +- Support for Python slice, tuple and list indexing for `std::vector` bindings ([#592](https://github.com/stack-of-tasks/eigenpy/pull/592)) ## [3.12.0] - 2025-08-12 From a57d0df5e177f1d72739460f0558e712cf8a5840 Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Thu, 25 Sep 2025 09:31:26 +0100 Subject: [PATCH 6/7] [unittest] Fixed NPY002 error --- unittest/python/test_std_vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py index 8bd104eba..3fc195d50 100644 --- a/unittest/python/test_std_vector.py +++ b/unittest/python/test_std_vector.py @@ -102,7 +102,7 @@ def checkZero(v): # Test slicing l6 = eigenpy.StdVec_VectorXd() for _ in range(4): - l6.append(np.random.randn(3)) + l6.append(rng.random(3)) checkAllValues(l6[:1], l6.tolist()[:1]) checkAllValues(l6[1:], l6.tolist()[1:]) checkAllValues(l6[:-1], l6.tolist()[:-1]) From 69d39499106c03b3eb81b853eefcbccec5e25eee Mon Sep 17 00:00:00 2001 From: Carlos Mastalli Date: Thu, 25 Sep 2025 09:38:10 +0100 Subject: [PATCH 7/7] [pre-commit] Run pre-commit --- include/eigenpy/std-vector.hpp | 39 ++++++++++++++++++------------ unittest/python/test_std_vector.py | 2 +- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/include/eigenpy/std-vector.hpp b/include/eigenpy/std-vector.hpp index 8481e8ec3..278f942fd 100644 --- a/include/eigenpy/std-vector.hpp +++ b/include/eigenpy/std-vector.hpp @@ -86,9 +86,9 @@ struct overload_base_get_item_for_std_vector template void visit(Class &cl) const { cl.def("__getitem__", &base_get_item_int) - .def("__getitem__", &base_get_item_slice) - .def("__getitem__", &base_get_item_list) - .def("__getitem__", &base_get_item_tuple); + .def("__getitem__", &base_get_item_slice) + .def("__getitem__", &base_get_item_list) + .def("__getitem__", &base_get_item_tuple); } private: @@ -109,20 +109,24 @@ struct overload_base_get_item_for_std_vector } static boost::python::object base_get_item_slice( - boost::python::back_reference container, + boost::python::back_reference container, boost::python::slice slice) { bp::list out; try { - auto rng = slice.get_indices(container.get().begin(), container.get().end()); - // rng.start, rng.stop are iterators; rng.step is int; [start, stop] is closed - typename bp::to_python_indirect convert; + auto rng = + slice.get_indices(container.get().begin(), container.get().end()); + // rng.start, rng.stop are iterators; rng.step is int; [start, stop] is + // closed + typename bp::to_python_indirect + convert; // forward or backward - for (typename Container::iterator it = rng.start;; std::advance(it, rng.step)) { + for (typename Container::iterator it = rng.start;; + std::advance(it, rng.step)) { out.append(bp::object(bp::handle<>(convert(*it)))); - if (it == rng.stop) break; // closed interval, include stop + if (it == rng.stop) break; // closed interval, include stop } - } catch (const std::invalid_argument&) { + } catch (const std::invalid_argument &) { // Boost.Python specifies empty ranges throw invalid_argument. // Return [] (matches Python's behavior for empty slices). return bp::list(); @@ -130,7 +134,8 @@ struct overload_base_get_item_for_std_vector return out; } - static bp::object base_get_item_list(bp::back_reference c, bp::list idxs) { + static bp::object base_get_item_list(bp::back_reference c, + bp::list idxs) { const Py_ssize_t m = bp::len(idxs); bp::list out; for (Py_ssize_t k = 0; k < m; ++k) { @@ -146,7 +151,8 @@ struct overload_base_get_item_for_std_vector return out; } - static bp::object base_get_item_tuple(bp::back_reference c, bp::tuple idxs) { + static bp::object base_get_item_tuple(bp::back_reference c, + bp::tuple idxs) { const Py_ssize_t m = bp::len(idxs); bp::list out; for (Py_ssize_t k = 0; k < m; ++k) { @@ -172,9 +178,10 @@ struct overload_base_get_item_for_std_vector return static_cast(idx); } - static bp::object elem_ref(Container& c, index_type i) { - typename bp::to_python_indirect conv; + static bp::object elem_ref(Container &c, index_type i) { + typename bp::to_python_indirect + conv; return bp::object(bp::handle<>(conv(c[i]))); } diff --git a/unittest/python/test_std_vector.py b/unittest/python/test_std_vector.py index 3fc195d50..3af9723cf 100644 --- a/unittest/python/test_std_vector.py +++ b/unittest/python/test_std_vector.py @@ -114,4 +114,4 @@ def checkZero(v): T = (0, 2) L6_copy = l6[T] for k, i in enumerate(L): - checkAllValues(L6_copy[k], l6[i]) \ No newline at end of file + checkAllValues(L6_copy[k], l6[i])