/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include /* so ArrayView is convertible from python array */ #include #include #include #include "Corrade/Containers/Python.h" #include "corrade/bootstrap.h" #include "corrade/PyBuffer.h" namespace corrade { namespace { const char* const FormatStrings[]{ /* 0. Representing bytes as unsigned. Not using 'c' because then it behaves differently from bytes/bytearray, where you can do `a[0] = ord('A')`. */ "B", }; template constexpr std::size_t formatIndex(); template<> constexpr std::size_t formatIndex() { return 0; } struct Slice { std::size_t start; std::size_t stop; std::ptrdiff_t step; }; Slice calculateSlice(py::slice slice, std::size_t containerSize) { std::size_t size; std::ptrdiff_t start, stop, step; /* Happens for example when passing a tuple as a slice or a zero step */ if(!slice.compute(containerSize, reinterpret_cast(&start), reinterpret_cast(&stop), reinterpret_cast(&step), &size)) throw py::error_already_set{}; /* If step is negative, start > stop and we have to recalculate */ CORRADE_INTERNAL_ASSERT((start <= stop) == (step > 0)); if(step < 0) { std::swap(start, stop); start += 1; stop += 1; } return Slice{std::size_t(start), std::size_t(stop), step}; } template bool arrayViewBufferProtocol(T& self, Py_buffer& buffer, int flags) { if((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && !std::is_const::value) { PyErr_SetString(PyExc_BufferError, "array view is not writable"); return false; } /* I hate the const_casts but I assume this is to make editing easier, NOT to make it possible for users to stomp on these values. */ buffer.ndim = 1; buffer.itemsize = sizeof(typename T::Type); buffer.len = sizeof(typename T::Type)*self.size(); buffer.buf = const_cast::type*>(self.data()); buffer.readonly = std::is_const::value; if((flags & PyBUF_FORMAT) == PyBUF_FORMAT) buffer.format = const_cast(FormatStrings[formatIndex::type>()]); if(flags != PyBUF_SIMPLE) { /* The view is immutable (can't change its size after it has been constructed), so referencing the size directly is okay */ buffer.shape = reinterpret_cast(&Containers::Implementation::sizeRef(self)); if((flags & PyBUF_STRIDES) == PyBUF_STRIDES) buffer.strides = &buffer.itemsize; } return true; } template void arrayView(py::class_, Containers::PyArrayViewHolder>>& c) { /* Implicitly convertible from a buffer */ py::implicitly_convertible>(); /* This is needed for implicit conversion from np.array */ py::implicitly_convertible>(); c /* Constructor */ .def(py::init(), "Default constructor") /* Buffer protocol */ .def(py::init([](py::buffer other) { /* GCC 4.8 otherwise loudly complains about missing initializers */ Py_buffer buffer{nullptr, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr, nullptr}; if(PyObject_GetBuffer(other.ptr(), &buffer, (std::is_const::value ? 0 : PyBUF_WRITABLE)) != 0) throw py::error_already_set{}; Containers::ScopeGuard e{&buffer, PyBuffer_Release}; /* I would test for dimensions here but np.array() sometimes gives 0 for an one-dimensional array so ¯\_(ツ)_/¯ */ if(buffer.strides && buffer.strides[0] != buffer.itemsize) throw py::buffer_error{Utility::formatString("expected stride of {} but got {}", buffer.itemsize, buffer.strides[0])}; /* reinterpret_borrow converts PyObject* to an (automatically refcounted) py::object. We take the underlying object instead of the buffer because we no longer care about the buffer descriptor -- that could allow the GC to haul away a bit more garbage */ return Containers::pyArrayViewHolder(Containers::ArrayView{static_cast(buffer.buf), std::size_t(buffer.len)}, py::reinterpret_borrow(buffer.obj)); }), "Construct from a buffer") /* Length and memory owning object */ .def("__len__", &Containers::ArrayView::size, "View size") .def_property_readonly("owner", [](const Containers::ArrayView& self) { return pyObjectHolderFor(self).owner; }, "Memory owner object") /* Conversion to bytes */ .def("__bytes__", [](const Containers::ArrayView& self) { return py::bytes(self.data(), self.size()); }, "Convert to bytes") /* Single item retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ .def("__getitem__", [](const Containers::ArrayView& self, std::size_t i) { if(i >= self.size()) throw pybind11::index_error{}; return self[i]; }, "Value at given position") /* Slicing */ .def("__getitem__", [](const Containers::ArrayView& self, py::slice slice) -> py::object { const Slice calculated = calculateSlice(slice, self.size()); /* Non-trivial stride, return a different type */ if(calculated.step != 1) { return pyCastButNotShitty(Containers::pyArrayViewHolder(Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor(self).owner)); } /* Usual business */ return pyCastButNotShitty(Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop), pyObjectHolderFor(self).owner)); }, "Slice the view"); enableBetterBufferProtocol, arrayViewBufferProtocol>(c); } template void mutableArrayView(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__setitem__", [](const Containers::ArrayView& self, std::size_t i, const T& value) { if(i >= self.size()) throw pybind11::index_error{}; self[i] = value; }, "Set a value at given position"); } /* Tuple for given dimension */ template struct DimensionsTuple; template struct DimensionsTuple<1, T> { typedef std::tuple Type; }; template struct DimensionsTuple<2, T> { typedef std::tuple Type; }; template struct DimensionsTuple<3, T> { typedef std::tuple Type; }; template struct DimensionsTuple<4, T> { typedef std::tuple Type; }; /* Size tuple for given dimension */ template typename DimensionsTuple::Type size(Containers::StridedDimensions); template<> std::tuple size(Containers::StridedDimensions<1, std::size_t> size) { return std::make_tuple(size[0]); } template<> std::tuple size(Containers::StridedDimensions<2, std::size_t> size) { return std::make_tuple(size[0], size[1]); } template<> std::tuple size(Containers::StridedDimensions<3, std::size_t> size) { return std::make_tuple(size[0], size[1], size[2]); } template<> std::tuple size(Containers::StridedDimensions<4, std::size_t> size) { return std::make_tuple(size[0], size[1], size[2], size[3]); } /* Stride tuple for given dimension */ template typename DimensionsTuple::Type stride(Containers::StridedDimensions); template<> std::tuple stride(Containers::StridedDimensions<1, std::ptrdiff_t> stride) { return std::make_tuple(stride[0]); } template<> std::tuple stride(Containers::StridedDimensions<2, std::ptrdiff_t> stride) { return std::make_tuple(stride[0], stride[1]); } template<> std::tuple stride(Containers::StridedDimensions<3, std::ptrdiff_t> stride) { return std::make_tuple(stride[0], stride[1], stride[2]); } template<> std::tuple stride(Containers::StridedDimensions<4, std::ptrdiff_t> stride) { return std::make_tuple(stride[0], stride[1], stride[2], stride[3]); } /* Byte conversion for given dimension */ template Containers::Array bytes(Containers::StridedArrayView); template<> Containers::Array bytes(Containers::StridedArrayView1D view) { Containers::Array out{view.size()}; std::size_t pos = 0; for(const char i: view) out[pos++] = i; return out; } template<> Containers::Array bytes(Containers::StridedArrayView2D view) { Containers::Array out{view.size()[0]*view.size()[1]}; std::size_t pos = 0; for(Containers::StridedArrayView1D i: view) for(const char j: i) out[pos++] = j; return out; } template<> Containers::Array bytes(Containers::StridedArrayView3D view) { Containers::Array out{view.size()[0]*view.size()[1]*view.size()[2]}; std::size_t pos = 0; for(Containers::StridedArrayView2D i: view) for(Containers::StridedArrayView1D j: i) for(const char k: j) out[pos++] = k; return out; } template<> Containers::Array bytes(Containers::StridedArrayView<4, const char> view) { Containers::Array out{view.size()[0]*view.size()[1]*view.size()[2]*view.size()[3]}; std::size_t pos = 0; for(Containers::StridedArrayView3D i: view) for(Containers::StridedArrayView2D j: i) for(Containers::StridedArrayView1D k: j) for(const char l: k) out[pos++] = l; return out; } /* Getting a runtime tuple index. Ugh. */ template const T& dimensionsTupleGet(const typename DimensionsTuple<1, T>::Type& tuple, std::size_t i) { if(i == 0) return std::get<0>(tuple); CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } template const T& dimensionsTupleGet(const typename DimensionsTuple<2, T>::Type& tuple, std::size_t i) { if(i == 0) return std::get<0>(tuple); if(i == 1) return std::get<1>(tuple); CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } template const T& dimensionsTupleGet(const typename DimensionsTuple<3, T>::Type& tuple, std::size_t i) { if(i == 0) return std::get<0>(tuple); if(i == 1) return std::get<1>(tuple); if(i == 2) return std::get<2>(tuple); CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } template const T& dimensionsTupleGet(const typename DimensionsTuple<4, T>::Type& tuple, std::size_t i) { if(i == 0) return std::get<0>(tuple); if(i == 1) return std::get<1>(tuple); if(i == 2) return std::get<2>(tuple); if(i == 3) return std::get<3>(tuple); CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } template bool stridedArrayViewBufferProtocol(T& self, Py_buffer& buffer, int flags) { if((flags & PyBUF_STRIDES) != PyBUF_STRIDES) { /* TODO: allow this if the array actually *is* contiguous? */ PyErr_SetString(PyExc_BufferError, "array view is not contiguous"); return false; } if((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && !std::is_const::value) { PyErr_SetString(PyExc_BufferError, "array view is not writable"); return false; } /* I hate the const_casts but I assume this is to make editing easier, NOT to make it possible for users to stomp on these values. */ buffer.ndim = T::Dimensions; buffer.itemsize = sizeof(typename T::Type); buffer.len = sizeof(typename T::Type); for(std::size_t i = 0; i != T::Dimensions; ++i) buffer.len *= Containers::Implementation::sizeRef(self)[i]; buffer.buf = const_cast::type*>(self.data()); buffer.readonly = std::is_const::value; if((flags & PyBUF_FORMAT) == PyBUF_FORMAT) buffer.format = const_cast(FormatStrings[formatIndex::type>()]); /* The view is immutable (can't change its size after it has been constructed), so referencing the size/stride directly is okay */ buffer.shape = const_cast(reinterpret_cast(Containers::Implementation::sizeRef(self).begin())); buffer.strides = const_cast(reinterpret_cast(Containers::Implementation::strideRef(self).begin())); return true; } inline std::size_t largerStride(std::size_t a, std::size_t b) { return a < b ? b : a; /* max(), but named like this to avoid clashes */ } template void stridedArrayView(py::class_, Containers::PyArrayViewHolder>>& c) { /* Implicitly convertible from a buffer */ py::implicitly_convertible>(); /* This is needed for implicit conversion from np.array */ py::implicitly_convertible>(); c /* Constructor */ .def(py::init(), "Default constructor") /* Buffer protocol */ .def(py::init([](py::buffer other) { /* GCC 4.8 otherwise loudly complains about missing initializers */ Py_buffer buffer{nullptr, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr, nullptr}; if(PyObject_GetBuffer(other.ptr(), &buffer, PyBUF_STRIDES|(std::is_const::value ? 0 : PyBUF_WRITABLE)) != 0) throw py::error_already_set{}; Containers::ScopeGuard e{&buffer, PyBuffer_Release}; if(buffer.ndim != dimensions) throw py::buffer_error{Utility::formatString("expected {} dimensions but got {}", dimensions, buffer.ndim)}; Containers::StaticArrayView sizes{reinterpret_cast(buffer.shape)}; Containers::StaticArrayView strides{reinterpret_cast(buffer.strides)}; /* Calculate total memory size that spans the whole view. Mainly to make the constructor assert happy, not used otherwise */ std::size_t size = 0; for(std::size_t i = 0; i != dimensions; ++i) size = largerStride(buffer.shape[i]*(buffer.strides[i] < 0 ? -buffer.strides[i] : buffer.strides[i]), size); /* reinterpret_borrow converts PyObject* to an (automatically refcounted) py::object. We take the underlying object instead of the buffer because we no longer care about the buffer descriptor -- that could allow the GC to haul away a bit more garbage */ return Containers::pyArrayViewHolder(Containers::StridedArrayView{ {static_cast(buffer.buf), size}, Containers::StaticArrayView{reinterpret_cast(buffer.shape)}, Containers::StaticArrayView{reinterpret_cast(buffer.strides)}}, py::reinterpret_borrow(buffer.obj)); }), "Construct from a buffer") /* Length, size/stride tuple, dimension count and memory owning object */ .def("__len__", [](const Containers::StridedArrayView& self) { return Containers::StridedDimensions(self.size())[0]; }, "View size in the top-level dimension") .def_property_readonly("size", [](const Containers::StridedArrayView& self) { return size(self.size()); }, "View size in each dimension") .def_property_readonly("stride", [](const Containers::StridedArrayView& self) { return stride(self.stride()); }, "View stride in each dimension") .def_property_readonly("dimensions", [](const Containers::StridedArrayView&) { return dimensions; }, "Dimension count") .def_property_readonly("owner", [](const Containers::StridedArrayView& self) { return pyObjectHolderFor(self).owner; }, "Memory owner object") /* Conversion to bytes */ .def("__bytes__", [](const Containers::StridedArrayView& self) { /* TODO: use _PyBytes_Resize() to avoid the double copy */ const Containers::Array out = bytes(Containers::arrayCast(self)); return py::bytes(out.data(), out.size()); }, "Convert to bytes") /* Slicing of the top dimension */ .def("__getitem__", [](const Containers::StridedArrayView& self, py::slice slice) { const Slice calculated = calculateSlice(slice, Containers::StridedDimensions{self.size()}[0]); return Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor(self).owner); }, "Slice the view"); enableBetterBufferProtocol, stridedArrayViewBufferProtocol>(c); } template void stridedArrayView1D(py::class_, Containers::PyArrayViewHolder>>& c) { c /* Single item retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ .def("__getitem__", [](const Containers::StridedArrayView<1, T>& self, std::size_t i) { if(i >= self.size()) throw pybind11::index_error{}; return self[i]; }, "Value at given position"); } template void stridedArrayViewND(py::class_, Containers::PyArrayViewHolder>>& c) { c /* Sub-view retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ .def("__getitem__", [](const Containers::StridedArrayView& self, std::size_t i) { if(i >= Containers::StridedDimensions{self.size()}[0]) throw pybind11::index_error{}; return Containers::pyArrayViewHolder(self[i], pyObjectHolderFor(self).owner); }, "Sub-view at given position") /* Single-item retrieval. Need to throw IndexError in order to allow iteration: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ */ /* Multi-dimensional slicing */ .def("__getitem__", [](const Containers::StridedArrayView& self, const typename DimensionsTuple::Type& slice) { Containers::StridedDimensions starts; Containers::StridedDimensions stops; Containers::StridedDimensions steps; for(std::size_t i = 0; i != dimensions; ++i) { const Slice calculated = calculateSlice(dimensionsTupleGet(slice, i), self.size()[i]); starts[i] = calculated.start; stops[i] = calculated.stop; steps[i] = calculated.step; } return Containers::pyArrayViewHolder(self.slice(starts, stops).every(steps), pyObjectHolderFor(self).owner); }, "Slice the view"); } template void stridedArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__getitem__", [](const Containers::StridedArrayView<2, T>& self, const std::tuple& i) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1]) throw py::index_error{}; return self[std::get<0>(i)][std::get<1>(i)]; }, "Value at given position") .def("transposed", [](const Containers::StridedArrayView<2, T>& self, const std::size_t a, std::size_t b) { if((a == 0 && b == 1) || (a == 1 && b == 0)) return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 2)}; }, "Transpose two dimensions") .def("flipped", [](const Containers::StridedArrayView<2, T>& self, const std::size_t dimension) { if(dimension == 0) return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); if(dimension == 1) return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 2)}; }, "Flip a dimension") .def("broadcasted", [](const Containers::StridedArrayView<2, T>& self, const std::size_t dimension, std::size_t size) { if(dimension == 0) return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); if(dimension == 1) return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 2)}; }, "Broadcast a dimension"); } template void stridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__getitem__", [](const Containers::StridedArrayView<3, T>& self, const std::tuple& i) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2]) throw pybind11::index_error{}; return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)]; }, "Value at given position") .def("transposed", [](const Containers::StridedArrayView<3, T>& self, const std::size_t a, std::size_t b) { if((a == 0 && b == 1) || (a == 1 && b == 0)) return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); if((a == 0 && b == 2) || (a == 2 && b == 0)) return Containers::pyArrayViewHolder(self.template transposed<0, 2>(), pyObjectHolderFor(self).owner); if((a == 1 && b == 2) || (a == 2 && b == 1)) return Containers::pyArrayViewHolder(self.template transposed<1, 2>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 3)}; }, "Transpose two dimensions") .def("flipped", [](const Containers::StridedArrayView<3, T>& self, const std::size_t dimension) { if(dimension == 0) return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); if(dimension == 1) return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); if(dimension == 2) return Containers::pyArrayViewHolder(self.template flipped<2>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 3)}; }, "Flip a dimension") .def("broadcasted", [](const Containers::StridedArrayView<3, T>& self, const std::size_t dimension, std::size_t size) { if(dimension == 0) return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); if(dimension == 1) return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); if(dimension == 2) return Containers::pyArrayViewHolder(self.template broadcasted<2>(size), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 3)}; }, "Broadcast a dimension"); } template void stridedArrayView4D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__getitem__", [](const Containers::StridedArrayView<4, T>& self, const std::tuple& i) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2] || std::get<3>(i) >= self.size()[3]) throw pybind11::index_error{}; return self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)]; }, "Value at given position") .def("transposed", [](const Containers::StridedArrayView<4, T>& self, const std::size_t a, std::size_t b) { if((a == 0 && b == 1) || (a == 1 && b == 0)) return Containers::pyArrayViewHolder(self.template transposed<0, 1>(), pyObjectHolderFor(self).owner); if((a == 0 && b == 2) || (a == 2 && b == 0)) return Containers::pyArrayViewHolder(self.template transposed<0, 2>(), pyObjectHolderFor(self).owner); if((a == 0 && b == 3) || (a == 3 && b == 0)) return Containers::pyArrayViewHolder(self.template transposed<0, 3>(), pyObjectHolderFor(self).owner); if((a == 1 && b == 2) || (a == 2 && b == 1)) return Containers::pyArrayViewHolder(self.template transposed<1, 2>(), pyObjectHolderFor(self).owner); if((a == 1 && b == 3) || (a == 3 && b == 1)) return Containers::pyArrayViewHolder(self.template transposed<1, 3>(), pyObjectHolderFor(self).owner); if((a == 2 && b == 3) || (a == 3 && b == 2)) return Containers::pyArrayViewHolder(self.template transposed<2, 3>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimensions {}, {} can't be transposed in a {}D view", a, b, 4)}; }, "Transpose two dimensions") .def("flipped", [](const Containers::StridedArrayView<4, T>& self, const std::size_t dimension) { if(dimension == 0) return Containers::pyArrayViewHolder(self.template flipped<0>(), pyObjectHolderFor(self).owner); if(dimension == 1) return Containers::pyArrayViewHolder(self.template flipped<1>(), pyObjectHolderFor(self).owner); if(dimension == 2) return Containers::pyArrayViewHolder(self.template flipped<2>(), pyObjectHolderFor(self).owner); if(dimension == 3) return Containers::pyArrayViewHolder(self.template flipped<3>(), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 4)}; }, "Flip a dimension") .def("broadcasted", [](const Containers::StridedArrayView<4, T>& self, const std::size_t dimension, std::size_t size) { if(dimension == 0) return Containers::pyArrayViewHolder(self.template broadcasted<0>(size), pyObjectHolderFor(self).owner); if(dimension == 1) return Containers::pyArrayViewHolder(self.template broadcasted<1>(size), pyObjectHolderFor(self).owner); if(dimension == 2) return Containers::pyArrayViewHolder(self.template broadcasted<2>(size), pyObjectHolderFor(self).owner); if(dimension == 3) return Containers::pyArrayViewHolder(self.template broadcasted<3>(size), pyObjectHolderFor(self).owner); throw py::value_error{Utility::formatString("dimension {} out of range for a {}D view", dimension, 4)}; }, "Broadcast a dimension"); } template void mutableStridedArrayView1D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__setitem__", [](const Containers::StridedArrayView<1, T>& self, const std::size_t i, const T& value) { if(i >= self.size()) throw pybind11::index_error{}; self[i] = value; }, "Set a value at given position"); } template void mutableStridedArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__setitem__", [](const Containers::StridedArrayView<2, T>& self, const std::tuple& i, const T& value) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1]) throw pybind11::index_error{}; self[std::get<0>(i)][std::get<1>(i)] = value; }, "Set a value at given position"); } template void mutableStridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__setitem__", [](const Containers::StridedArrayView<3, T>& self, const std::tuple& i, const T& value) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2]) throw pybind11::index_error{}; self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)] = value; }, "Set a value at given position"); } template void mutableStridedArrayView4D(py::class_, Containers::PyArrayViewHolder>>& c) { c .def("__setitem__", [](const Containers::StridedArrayView<4, T>& self, const std::tuple& i, const T& value) { if(std::get<0>(i) >= self.size()[0] || std::get<1>(i) >= self.size()[1] || std::get<2>(i) >= self.size()[2] || std::get<3>(i) >= self.size()[3]) throw pybind11::index_error{}; self[std::get<0>(i)][std::get<1>(i)][std::get<2>(i)][std::get<3>(i)] = value; }, "Set a value at given position"); } } void containers(py::module& m) { m.doc() = "Container implementations"; py::class_, Containers::PyArrayViewHolder>> arrayView_{m, "ArrayView", "Array view", py::buffer_protocol{}}; arrayView(arrayView_); py::class_, Containers::PyArrayViewHolder>> mutableArrayView_{m, "MutableArrayView", "Mutable array view", py::buffer_protocol{}}; arrayView(mutableArrayView_); mutableArrayView(mutableArrayView_); py::class_, Containers::PyArrayViewHolder>> stridedArrayView1D_{m, "StridedArrayView1D", "One-dimensional array view with stride information", py::buffer_protocol{}}; py::class_, Containers::PyArrayViewHolder>> stridedArrayView2D_{m, "StridedArrayView2D", "Two-dimensional array view with stride information", py::buffer_protocol{}}; py::class_, Containers::PyArrayViewHolder>> stridedArrayView3D_{m, "StridedArrayView3D", "Three-dimensional array view with stride information", py::buffer_protocol{}}; py::class_, Containers::PyArrayViewHolder>> stridedArrayView4D_{m, "StridedArrayView4D", "Four-dimensional array view with stride information", py::buffer_protocol{}}; stridedArrayView(stridedArrayView1D_); stridedArrayView1D(stridedArrayView1D_); stridedArrayView(stridedArrayView2D_); stridedArrayViewND(stridedArrayView2D_); stridedArrayView2D(stridedArrayView2D_); stridedArrayView(stridedArrayView3D_); stridedArrayViewND(stridedArrayView3D_); stridedArrayView3D(stridedArrayView3D_); stridedArrayView(stridedArrayView4D_); stridedArrayViewND(stridedArrayView4D_); stridedArrayView4D(stridedArrayView4D_); py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView1D_{m, "MutableStridedArrayView1D", "Mutable one-dimensional array view with stride information", py::buffer_protocol{}}; py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView2D_{m, "MutableStridedArrayView2D", "Mutable two-dimensional array view with stride information", py::buffer_protocol{}}; py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView3D_{m, "MutableStridedArrayView3D", "Mutable three-dimensional array view with stride information", py::buffer_protocol{}}; py::class_, Containers::PyArrayViewHolder>> mutableStridedArrayView4D_{m, "MutableStridedArrayView4D", "Mutable four-dimensional array view with stride information", py::buffer_protocol{}}; stridedArrayView(mutableStridedArrayView1D_); stridedArrayView1D(mutableStridedArrayView1D_); stridedArrayView(mutableStridedArrayView2D_); stridedArrayViewND(mutableStridedArrayView2D_); stridedArrayView2D(mutableStridedArrayView2D_); stridedArrayView(mutableStridedArrayView3D_); stridedArrayViewND(mutableStridedArrayView3D_); stridedArrayView3D(mutableStridedArrayView3D_); stridedArrayView(mutableStridedArrayView4D_); stridedArrayViewND(mutableStridedArrayView4D_); stridedArrayView4D(mutableStridedArrayView4D_); mutableStridedArrayView1D(mutableStridedArrayView1D_); mutableStridedArrayView2D(mutableStridedArrayView2D_); mutableStridedArrayView3D(mutableStridedArrayView3D_); mutableStridedArrayView4D(mutableStridedArrayView4D_); } } #ifndef CORRADE_BUILD_STATIC /* TODO: remove declaration when https://github.com/pybind/pybind11/pull/1863 is released */ extern "C" PYBIND11_EXPORT PyObject* PyInit_containers(); PYBIND11_MODULE(containers, m) { corrade::containers(m); } #endif