From 6d8ab441e8ffd0a7b950de4ce37699ec34b960de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 31 May 2023 19:21:21 +0200 Subject: [PATCH] python: expose the new expanded() API on strided (bit) array views. Not collapsed() yet, as it has the return type depending on the count parameter, and I'm not yet decided how to express that here. --- doc/python/corrade.containers.rst | 48 ++++++ .../StridedArrayViewPythonBindings.h | 4 + src/python/corrade/containers.cpp | 137 +++++++++++++++- src/python/corrade/test/test_containers.py | 146 ++++++++++++++++++ 4 files changed, 333 insertions(+), 2 deletions(-) diff --git a/doc/python/corrade.containers.rst b/doc/python/corrade.containers.rst index 32b4e91..e96bf1b 100644 --- a/doc/python/corrade.containers.rst +++ b/doc/python/corrade.containers.rst @@ -104,6 +104,10 @@ :raise IndexError: if :p:`dimension` is not :py:`0` .. py:function:: corrade.containers.StridedArrayView1D.broadcasted :raise IndexError: if :p:`dimension` is not :py:`0` +.. py:function:: corrade.containers.StridedArrayView1D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.MutableStridedArrayView1D @@ -114,6 +118,10 @@ :raise IndexError: if :p:`dimension` is not :py:`0` .. py:function:: corrade.containers.MutableStridedArrayView1D.broadcasted :raise IndexError: if :p:`dimension` is not :py:`0` +.. py:function:: corrade.containers.MutableStridedArrayView1D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.StridedArrayView2D @@ -126,6 +134,10 @@ .. py:function:: corrade.containers.StridedArrayView2D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0` or :py:`1` or if they're the same +.. py:function:: corrade.containers.StridedArrayView2D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.MutableStridedArrayView2D @@ -139,6 +151,10 @@ .. py:function:: corrade.containers.MutableStridedArrayView2D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0` or :py:`1` or if they're the same +.. py:function:: corrade.containers.MutableStridedArrayView2D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.StridedArrayView3D @@ -151,6 +167,10 @@ .. py:function:: corrade.containers.StridedArrayView3D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` or :py:`2` or if they're the same +.. py:function:: corrade.containers.StridedArrayView3D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.MutableStridedArrayView3D @@ -164,6 +184,10 @@ .. py:function:: corrade.containers.MutableStridedArrayView3D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` or :py:`2` or if they're the same +.. py:function:: corrade.containers.MutableStridedArrayView3D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.StridedArrayView4D @@ -245,6 +269,10 @@ :raise IndexError: if :p:`dimension` is not :py:`0` .. py:function:: corrade.containers.StridedBitArrayView1D.broadcasted :raise IndexError: if :p:`dimension` is not :py:`0` +.. py:function:: corrade.containers.StridedBitArrayView1D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.MutableStridedBitArrayView1D @@ -255,6 +283,10 @@ :raise IndexError: if :p:`dimension` is not :py:`0` .. py:function:: corrade.containers.MutableStridedBitArrayView1D.broadcasted :raise IndexError: if :p:`dimension` is not :py:`0` +.. py:function:: corrade.containers.MutableStridedBitArrayView1D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.StridedBitArrayView2D @@ -267,6 +299,10 @@ .. py:function:: corrade.containers.StridedBitArrayView2D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0` or :py:`1` or if they're the same +.. py:function:: corrade.containers.StridedBitArrayView2D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.MutableStridedBitArrayView2D @@ -280,6 +316,10 @@ .. py:function:: corrade.containers.MutableStridedBitArrayView2D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0` or :py:`1` or if they're the same +.. py:function:: corrade.containers.MutableStridedBitArrayView2D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.StridedBitArrayView3D @@ -292,6 +332,10 @@ .. py:function:: corrade.containers.StridedBitArrayView3D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` or :py:`2` or if they're the same +.. py:function:: corrade.containers.StridedBitArrayView3D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.MutableStridedBitArrayView3D @@ -305,6 +349,10 @@ .. py:function:: corrade.containers.MutableStridedBitArrayView3D.transposed :raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` or :py:`2` or if they're the same +.. py:function:: corrade.containers.MutableStridedBitArrayView3D.expanded + :raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2` + :raise ValueError: if product of :p:`size` is not equal to size in + :p:`dimension` .. py:class:: corrade.containers.StridedBitArrayView4D diff --git a/src/Corrade/Containers/StridedArrayViewPythonBindings.h b/src/Corrade/Containers/StridedArrayViewPythonBindings.h index ff6e603..cc82cc2 100644 --- a/src/Corrade/Containers/StridedArrayViewPythonBindings.h +++ b/src/Corrade/Containers/StridedArrayViewPythonBindings.h @@ -133,6 +133,10 @@ template class PyStridedArrayView: public StridedA return PyStridedArrayView{StridedArrayView::template broadcasted(size), format, itemsize, getitem, setitem}; } + template PyStridedArrayView expanded(const Containers::Size& size) const { + return PyStridedArrayView{StridedArrayView::template expanded(size), format, itemsize, getitem, setitem}; + } + /* has to be public as it's accessed by the bindings directly */ const char* format; std::size_t itemsize; diff --git a/src/python/corrade/containers.cpp b/src/python/corrade/containers.cpp index 92cc6f1..261ad70 100644 --- a/src/python/corrade/containers.cpp +++ b/src/python/corrade/containers.cpp @@ -374,6 +374,23 @@ template<> struct StridedOperation<1> { PyErr_Format(PyExc_IndexError, "dimension %u out of range for a %iD view", dimension, dimensions); throw py::error_already_set{}; } + template class View, unsigned dimensions, class T> static View expanded(const View& view, unsigned dimension, const typename DimensionsTuple::Type& size) { + if(dimension == 0) { + Containers::Size iSize{NoInit}; + std::size_t totalSize = 1; + for(std::size_t i = 0; i != count; ++i) { + iSize[i] = dimensionsTupleGet(size, i); + totalSize *= iSize[i]; + } + if(totalSize != Containers::Size{view.size()}[0]) { + PyErr_Format(PyExc_ValueError, "total size %zu doesn't match dimension %u with %zu elements", totalSize, dimension, Containers::Size{view.size()}[0]); + throw py::error_already_set{}; + } + return view.template expanded<0>(iSize); + } + PyErr_Format(PyExc_IndexError, "dimension %u out of range for a %iD view", dimension, dimensions); + throw py::error_already_set{}; + } }; template<> struct StridedOperation<2> { template class View, unsigned dimensions, class T> static View transposed(const View& view, unsigned a, unsigned b) { @@ -398,6 +415,22 @@ template<> struct StridedOperation<2> { } return StridedOperation<1>::broadcasted(view, dimension, size); } + template class View, unsigned dimensions, class T> static View expanded(const View& view, unsigned dimension, const typename DimensionsTuple::Type& size) { + if(dimension == 1) { + Containers::Size iSize{NoInit}; + std::size_t totalSize = 1; + for(std::size_t i = 0; i != count; ++i) { + iSize[i] = dimensionsTupleGet(size, i); + totalSize *= iSize[i]; + } + if(totalSize != view.size()[1]) { + PyErr_Format(PyExc_ValueError, "total size %zu doesn't match dimension %u with %zu elements", totalSize, dimension, view.size()[1]); + throw py::error_already_set{}; + } + return view.template expanded<1>(iSize); + } + return StridedOperation<1>::expanded(view, dimension, size); + } }; template<> struct StridedOperation<3> { template class View, unsigned dimensions, class T> static View transposed(const View& view, unsigned a, unsigned b) { @@ -424,6 +457,22 @@ template<> struct StridedOperation<3> { } return StridedOperation<2>::broadcasted(view, dimension, size); } + template class View, unsigned dimensions, class T> static View expanded(const View& view, unsigned dimension, const typename DimensionsTuple::Type& size) { + if(dimension == 2) { + Containers::Size iSize{NoInit}; + std::size_t totalSize = 1; + for(std::size_t i = 0; i != count; ++i) { + iSize[i] = dimensionsTupleGet(size, i); + totalSize *= iSize[i]; + } + if(totalSize != view.size()[2]) { + PyErr_Format(PyExc_ValueError, "total size %zu doesn't match dimension %u with %zu elements", totalSize, dimension, view.size()[2]); + throw py::error_already_set{}; + } + return view.template expanded<2>(iSize); + } + return StridedOperation<2>::expanded(view, dimension, size); + } }; template<> struct StridedOperation<4> { template class View, unsigned dimensions, class T> static View transposed(const View& view, unsigned a, unsigned b) { @@ -453,6 +502,22 @@ template<> struct StridedOperation<4> { } return StridedOperation<3>::broadcasted(view, dimension, size); } + template class View, unsigned dimensions, class T> static View expanded(const View& view, unsigned dimension, const typename DimensionsTuple::Type& size) { + if(dimension == 3) { + Containers::Size iSize{NoInit}; + std::size_t totalSize = 1; + for(std::size_t i = 0; i != count; ++i) { + iSize[i] = dimensionsTupleGet(size, i); + totalSize *= iSize[i]; + } + if(totalSize != view.size()[3]) { + PyErr_Format(PyExc_ValueError, "total size %zu doesn't match dimension %u with %zu elements", totalSize, dimension, view.size()[3]); + throw py::error_already_set{}; + } + return view.template expanded<3>(iSize); + } + return StridedOperation<3>::expanded(view, dimension, size); + } }; template class Steps, class T> Containers::PyArrayViewHolder stridedArrayViewSlice(const T& self, const typename DimensionsTuple::Type& slice, py::object owner) { @@ -589,7 +654,37 @@ template void stridedArrayView1D(py::class_& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<1>::expanded<2>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")) + .def("expanded", [](const Containers::PyStridedArrayView<1, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<1>::expanded<3>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")) + .def("expanded", [](const Containers::PyStridedArrayView<1, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<1>::expanded<4>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")); +} + +template void stridedArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { + c + /* Fancy operations */ + .def("expanded", [](const Containers::PyStridedArrayView<2, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<2>::expanded<2>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")) + .def("expanded", [](const Containers::PyStridedArrayView<2, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<2>::expanded<3>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")); +} + +template void stridedArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { + c + /* Fancy operations */ + .def("expanded", [](const Containers::PyStridedArrayView<3, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<3>::expanded<2>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")); } template void stridedArrayViewND(py::class_, Containers::PyArrayViewHolder>>& c) { @@ -695,7 +790,37 @@ template void stridedBitArrayView1D(py::class_& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<1>::expanded<2>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")) + .def("expanded", [](const Containers::BasicStridedBitArrayView<1, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<1>::expanded<3>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")) + .def("expanded", [](const Containers::BasicStridedBitArrayView<1, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<1>::expanded<4>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")); +} + +template void stridedBitArrayView2D(py::class_, Containers::PyArrayViewHolder>>& c) { + c + /* Fancy operations */ + .def("expanded", [](const Containers::BasicStridedBitArrayView<2, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<2>::expanded<2>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")) + .def("expanded", [](const Containers::BasicStridedBitArrayView<2, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<2>::expanded<3>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")); +} + +template void stridedBitArrayView3D(py::class_, Containers::PyArrayViewHolder>>& c) { + c + /* Fancy operations */ + .def("expanded", [](const Containers::BasicStridedBitArrayView<3, T>& self, unsigned dimension, const std::tuple& size) { + return Containers::pyArrayViewHolder(StridedOperation<3>::expanded<2>(self, dimension, size), pyObjectHolderFor(self).owner); + }, "Expand a dimension", py::arg("dimension"), py::arg("size")); } template void stridedBitArrayViewND(py::class_, Containers::PyArrayViewHolder>>& c) { @@ -852,8 +977,10 @@ void containers(py::module_& m) { return pyArrayViewHolder(Containers::StridedBitArrayView1D{other}, pyObjectHolderFor(other).owner); }), "Construct from a bit array view", py::arg("view")); stridedBitArrayView(stridedBitArrayView2D_); + stridedBitArrayView2D(stridedBitArrayView2D_); stridedBitArrayViewND(stridedBitArrayView2D_); stridedBitArrayView(stridedBitArrayView3D_); + stridedBitArrayView3D(stridedBitArrayView3D_); stridedBitArrayViewND(stridedBitArrayView3D_); stridedBitArrayView(stridedBitArrayView4D_); stridedBitArrayViewND(stridedBitArrayView4D_); @@ -869,8 +996,10 @@ void containers(py::module_& m) { stridedBitArrayView(mutableStridedBitArrayView1D_); stridedBitArrayView1D(mutableStridedBitArrayView1D_); stridedBitArrayView(mutableStridedBitArrayView2D_); + stridedBitArrayView2D(mutableStridedBitArrayView2D_); stridedBitArrayViewND(mutableStridedBitArrayView2D_); stridedBitArrayView(mutableStridedBitArrayView3D_); + stridedBitArrayView3D(mutableStridedBitArrayView3D_); stridedBitArrayViewND(mutableStridedBitArrayView3D_); stridedBitArrayView(mutableStridedBitArrayView4D_); stridedBitArrayViewND(mutableStridedBitArrayView4D_); @@ -897,8 +1026,10 @@ void containers(py::module_& m) { stridedArrayView(stridedArrayView1D_); stridedArrayView1D(stridedArrayView1D_); stridedArrayView(stridedArrayView2D_); + stridedArrayView2D(stridedArrayView2D_); stridedArrayViewND(stridedArrayView2D_); stridedArrayView(stridedArrayView3D_); + stridedArrayView3D(stridedArrayView3D_); stridedArrayViewND(stridedArrayView3D_); stridedArrayView(stridedArrayView4D_); stridedArrayViewND(stridedArrayView4D_); @@ -914,8 +1045,10 @@ void containers(py::module_& m) { stridedArrayView(mutableStridedArrayView1D_); stridedArrayView1D(mutableStridedArrayView1D_); stridedArrayView(mutableStridedArrayView2D_); + stridedArrayView2D(mutableStridedArrayView2D_); stridedArrayViewND(mutableStridedArrayView2D_); stridedArrayView(mutableStridedArrayView3D_); + stridedArrayView3D(mutableStridedArrayView3D_); stridedArrayViewND(mutableStridedArrayView3D_); stridedArrayView(mutableStridedArrayView4D_); stridedArrayViewND(mutableStridedArrayView4D_); diff --git a/src/python/corrade/test/test_containers.py b/src/python/corrade/test/test_containers.py index 519c201..63d799c 100644 --- a/src/python/corrade/test/test_containers.py +++ b/src/python/corrade/test/test_containers.py @@ -410,6 +410,24 @@ class StridedArrayView1D(unittest.TestCase): self.assertEqual(c.stride, (0,)) self.assertEqual(bytes(c), b'33333') + d2 = containers.StridedArrayView1D(a).expanded(0, (2, 4)) + self.assertIsInstance(d2, containers.StridedArrayView2D) + self.assertEqual(d2.size, (2, 4)) + self.assertEqual(d2.stride, (4, 1)) + self.assertEqual(bytes(d2), b'01234567') + + d3 = containers.StridedArrayView1D(a).expanded(0, (2, 2, 2)) + self.assertIsInstance(d3, containers.StridedArrayView3D) + self.assertEqual(d3.size, (2, 2, 2)) + self.assertEqual(d3.stride, (4, 2, 1)) + self.assertEqual(bytes(d3), b'01234567') + + d4 = containers.StridedArrayView1D(a).expanded(0, (2, 1, 2, 2)) + self.assertIsInstance(d4, containers.StridedArrayView4D) + self.assertEqual(d4.size, (2, 1, 2, 2)) + self.assertEqual(d4.stride, (4, 4, 2, 1)) + self.assertEqual(bytes(d4), b'01234567') + def test_ops_invalid(self): a = b'00' @@ -419,8 +437,12 @@ class StridedArrayView1D(unittest.TestCase): containers.StridedArrayView1D().flipped(1) with self.assertRaisesRegex(IndexError, "dimension 1 out of range for a 1D view"): containers.StridedArrayView1D(a).broadcasted(1, 3) + with self.assertRaisesRegex(IndexError, "dimension 1 out of range for a 1D view"): + containers.StridedArrayView1D(a).expanded(1, (1, 2)) with self.assertRaisesRegex(ValueError, "can't broadcast dimension 0 with 2 elements"): containers.StridedArrayView1D(a).broadcasted(0, 3) + with self.assertRaisesRegex(ValueError, "total size 3 doesn't match dimension 0 with 2 elements"): + containers.StridedArrayView1D(a).expanded(0, (1, 3)) def test_convert_memoryview(self): a = b'World is hell!' @@ -722,6 +744,18 @@ class StridedArrayView2D(unittest.TestCase): self.assertEqual(d.stride, (8, 0)) self.assertEqual(bytes(d), b'3377bb') + e3 = containers.StridedArrayView2D(v).expanded(1, (2, 4)) + self.assertIsInstance(e3, containers.StridedArrayView3D) + self.assertEqual(e3.size, (3, 2, 4)) + self.assertEqual(e3.stride, (8, 4, 1)) + self.assertEqual(bytes(e3), b'01234567456789ab89abcdef') + + e4 = containers.StridedArrayView2D(v).expanded(0, (1, 3, 1)) + self.assertIsInstance(e4, containers.StridedArrayView4D) + self.assertEqual(e4.size, (1, 3, 1, 8)) + self.assertEqual(e4.stride, (24, 8, 8, 1)) + self.assertEqual(bytes(e4), b'01234567456789ab89abcdef') + def test_ops_invalid(self): a = b'00' v = memoryview(a).cast('b', shape=[1, 2]) @@ -732,10 +766,16 @@ class StridedArrayView2D(unittest.TestCase): containers.StridedArrayView2D().flipped(2) with self.assertRaisesRegex(IndexError, "dimension 2 out of range for a 2D view"): containers.StridedArrayView2D(v).broadcasted(2, 3) + with self.assertRaisesRegex(IndexError, "dimension 2 out of range for a 2D view"): + containers.StridedArrayView2D(v).expanded(2, (1, 2)) with self.assertRaisesRegex(ValueError, "can't broadcast dimension 1 with 2 elements"): containers.StridedArrayView2D(v).broadcasted(1, 3) with self.assertRaisesRegex(IndexError, "dimensions 0, 2 can't be transposed in a 2D view"): containers.StridedArrayView2D().transposed(0, 2) + with self.assertRaisesRegex(ValueError, "total size 2 doesn't match dimension 0 with 1 elements"): + containers.StridedArrayView2D(v).expanded(0, (1, 2)) + with self.assertRaisesRegex(ValueError, "total size 4 doesn't match dimension 1 with 2 elements"): + containers.StridedArrayView2D(v).expanded(1, (2, 1, 2)) def test_convert_memoryview(self): a = memoryview(b'01234567' @@ -838,6 +878,12 @@ class StridedArrayView3D(unittest.TestCase): self.assertEqual(f.stride, (24, 8, 0)) self.assertEqual(bytes(f), b'000004444488888ccccc0000044444') + g4 = containers.StridedArrayView3D(v).expanded(2, (2, 4)) + self.assertIsInstance(g4, containers.StridedArrayView4D) + self.assertEqual(g4.size, (2, 3, 2, 4)) + self.assertEqual(g4.stride, (24, 8, 4, 1)) + self.assertEqual(bytes(g4), b'01234567456789ab89abcdefcdef012301234567456789ab') + def test_ops_invalid(self): a = b'00' v = memoryview(a).cast('b', shape=[1, 1, 2]) @@ -848,10 +894,16 @@ class StridedArrayView3D(unittest.TestCase): containers.StridedArrayView3D().flipped(3) with self.assertRaisesRegex(IndexError, "dimension 3 out of range for a 3D view"): containers.StridedArrayView3D(v).broadcasted(3, 3) + with self.assertRaisesRegex(IndexError, "dimension 3 out of range for a 3D view"): + containers.StridedArrayView3D(v).expanded(3, (1, 2)) with self.assertRaisesRegex(ValueError, "can't broadcast dimension 2 with 2 elements"): containers.StridedArrayView3D(v).broadcasted(2, 3) with self.assertRaisesRegex(IndexError, "dimensions 1, 3 can't be transposed in a 3D view"): containers.StridedArrayView3D().transposed(1, 3) + with self.assertRaisesRegex(ValueError, "total size 2 doesn't match dimension 0 with 1 elements"): + containers.StridedArrayView3D(v).expanded(0, (1, 2)) + with self.assertRaisesRegex(ValueError, "total size 3 doesn't match dimension 2 with 2 elements"): + containers.StridedArrayView3D(v).expanded(2, (3, 1)) # This is just a dumb copy of the above with one dimension inserted at the # second place. @@ -1713,6 +1765,45 @@ class StridedBitArrayView1D(unittest.TestCase): self.assertEqual(c[3], True) self.assertEqual(c[4], True) + d2 = v.expanded(0, (2, 4)) + self.assertIsInstance(d2, containers.StridedBitArrayView2D) + self.assertEqual(d2.size, (2, 4)) + self.assertEqual(d2.stride, (32, 8)) + self.assertEqual(d2[0][0], True) + self.assertEqual(d2[0][1], True) + self.assertEqual(d2[0][2], True) + self.assertEqual(d2[0][3], True) + self.assertEqual(d2[1][0], False) + self.assertEqual(d2[1][1], False) + self.assertEqual(d2[1][2], True) + self.assertEqual(d2[1][3], False) + + d3 = v.expanded(0, (2, 2, 2)) + self.assertIsInstance(d3, containers.StridedBitArrayView3D) + self.assertEqual(d3.size, (2, 2, 2)) + self.assertEqual(d3.stride, (32, 16, 8)) + self.assertEqual(d3[0][0][0], True) + self.assertEqual(d3[0][0][1], True) + self.assertEqual(d3[0][1][0], True) + self.assertEqual(d3[0][1][1], True) + self.assertEqual(d3[1][0][0], False) + self.assertEqual(d3[1][0][1], False) + self.assertEqual(d3[1][1][0], True) + self.assertEqual(d3[1][1][1], False) + + d4 = v.expanded(0, (2, 1, 2, 2)) + self.assertIsInstance(d4, containers.StridedBitArrayView4D) + self.assertEqual(d4.size, (2, 1, 2, 2)) + self.assertEqual(d4.stride, (32, 32, 16, 8)) + self.assertEqual(d4[0][0][0][0], True) + self.assertEqual(d4[0][0][0][1], True) + self.assertEqual(d4[0][0][1][0], True) + self.assertEqual(d4[0][0][1][1], True) + self.assertEqual(d4[1][0][0][0], False) + self.assertEqual(d4[1][0][0][1], False) + self.assertEqual(d4[1][0][1][0], True) + self.assertEqual(d4[1][0][1][1], False) + def test_ops_invalid(self): v = containers.StridedArrayView1D(b'00').slice_bit(0) @@ -1720,8 +1811,12 @@ class StridedBitArrayView1D(unittest.TestCase): containers.StridedBitArrayView1D().flipped(1) with self.assertRaisesRegex(IndexError, "dimension 1 out of range for a 1D view"): v.broadcasted(1, 3) + with self.assertRaisesRegex(IndexError, "dimension 1 out of range for a 1D view"): + v.expanded(1, (1, 2)) with self.assertRaisesRegex(ValueError, "can't broadcast dimension 0 with 2 elements"): v.broadcasted(0, 3) + with self.assertRaisesRegex(ValueError, "total size 3 doesn't match dimension 0 with 2 elements"): + v.expanded(0, (1, 3)) class StridedBitArrayView2D(unittest.TestCase): def test_init(self): @@ -1993,6 +2088,32 @@ class StridedBitArrayView2D(unittest.TestCase): self.assertEqual(d[2, 1], False) self.assertEqual(d[3, 1], False) + d3 = a.expanded(1, (2, 2)) + self.assertIsInstance(d3, containers.StridedBitArrayView3D) + self.assertEqual(d3.size, (2, 2, 2)) + self.assertEqual(d3.stride, (32, 16, 8)) + self.assertEqual(d3[0][0][0], True) + self.assertEqual(d3[0][0][1], True) + self.assertEqual(d3[0][1][0], True) + self.assertEqual(d3[0][1][1], True) + self.assertEqual(d3[1][0][0], False) + self.assertEqual(d3[1][0][1], False) + self.assertEqual(d3[1][1][0], True) + self.assertEqual(d3[1][1][1], False) + + d4 = a.expanded(1, (1, 2, 2)) + self.assertIsInstance(d4, containers.StridedBitArrayView4D) + self.assertEqual(d4.size, (2, 1, 2, 2)) + self.assertEqual(d4.stride, (32, 32, 16, 8)) + self.assertEqual(d4[0][0][0][0], True) + self.assertEqual(d4[0][0][0][1], True) + self.assertEqual(d4[0][0][1][0], True) + self.assertEqual(d4[0][0][1][1], True) + self.assertEqual(d4[1][0][0][0], False) + self.assertEqual(d4[1][0][0][1], False) + self.assertEqual(d4[1][0][1][0], True) + self.assertEqual(d4[1][0][1][1], False) + def test_ops_invalid(self): v = containers.StridedArrayView2D(memoryview(b'00').cast('b', shape=[1, 2])).slice_bit(0) @@ -2002,8 +2123,14 @@ class StridedBitArrayView2D(unittest.TestCase): containers.StridedBitArrayView2D().transposed(2, 1) with self.assertRaisesRegex(IndexError, "dimension 2 out of range for a 2D view"): v.broadcasted(2, 3) + with self.assertRaisesRegex(IndexError, "dimension 2 out of range for a 2D view"): + v.expanded(2, (1, 2)) with self.assertRaisesRegex(ValueError, "can't broadcast dimension 1 with 2 elements"): v.broadcasted(1, 3) + with self.assertRaisesRegex(ValueError, "total size 2 doesn't match dimension 0 with 1 elements"): + v.expanded(0, (1, 2)) + with self.assertRaisesRegex(ValueError, "total size 4 doesn't match dimension 1 with 2 elements"): + v.expanded(1, (2, 1, 2)) # Multi-dimensional behavior is tested extensively for StridedBitArrayView2D, # this checks just what differs, like constructors and fancy operations @@ -2072,6 +2199,19 @@ class StridedBitArrayView3D(unittest.TestCase): self.assertEqual(d[2, 0, 1], False) self.assertEqual(d[3, 0, 1], False) + e4 = a.expanded(2, (2, 2)) + self.assertIsInstance(e4, containers.StridedBitArrayView4D) + self.assertEqual(e4.size, (2, 1, 2, 2)) + self.assertEqual(e4.stride, (32, 32, 16, 8)) + self.assertEqual(e4[0][0][0][0], True) + self.assertEqual(e4[0][0][0][1], True) + self.assertEqual(e4[0][0][1][0], True) + self.assertEqual(e4[0][0][1][1], True) + self.assertEqual(e4[1][0][0][0], False) + self.assertEqual(e4[1][0][0][1], False) + self.assertEqual(e4[1][0][1][0], True) + self.assertEqual(e4[1][0][1][1], False) + def test_ops_invalid(self): v = containers.StridedArrayView3D(memoryview(b'00').cast('b', shape=[1, 1, 2])).slice_bit(0) @@ -2081,8 +2221,14 @@ class StridedBitArrayView3D(unittest.TestCase): containers.StridedBitArrayView3D().transposed(2, 3) with self.assertRaisesRegex(IndexError, "dimension 3 out of range for a 3D view"): v.broadcasted(3, 3) + with self.assertRaisesRegex(IndexError, "dimension 3 out of range for a 3D view"): + v.expanded(3, (1, 2)) with self.assertRaisesRegex(ValueError, "can't broadcast dimension 2 with 2 elements"): v.broadcasted(2, 3) + with self.assertRaisesRegex(ValueError, "total size 3 doesn't match dimension 0 with 1 elements"): + v.expanded(0, (1, 3)) + with self.assertRaisesRegex(ValueError, "total size 6 doesn't match dimension 2 with 2 elements"): + v.expanded(2, (2, 3)) # This is just a dumb copy of the above with one dimension inserted at the # second place.